Initial infrastructure documentation - comprehensive homelab reference
280
.claude-context.md
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
# Fred's Projects - Claude Code Context
|
||||||
|
**Source of Truth**: `C:\Users\Fred\projects`
|
||||||
|
|
||||||
|
This file provides context to Claude Code about all of Fred's active projects. When Fred starts a Claude Code session in VS Code Insiders, Claude should be aware of this ecosystem.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Active Projects
|
||||||
|
|
||||||
|
### 1. claude-workflows
|
||||||
|
**Path**: `C:\Users\Fred\projects\claude-workflows`
|
||||||
|
**Purpose**: Shared slash commands and ADHD-friendly productivity tools for Claude Code
|
||||||
|
**Status**: Active development
|
||||||
|
**Key Features**:
|
||||||
|
- `/push` - Quick commit and push
|
||||||
|
- `/eod` - End of day workflow
|
||||||
|
- ADHD assistant system with proactive interventions
|
||||||
|
- Auto-discovery scripts for cross-project setup
|
||||||
|
- Personality-driven assistant behavior (`personality.md`)
|
||||||
|
|
||||||
|
**Context Files**:
|
||||||
|
- `.assistant/personality.md` - Defines ADHD-friendly behavior rules
|
||||||
|
- `.assistant/state.json.template` - Session state tracking template
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. VA-Strategy
|
||||||
|
**Path**: `C:\Users\Fred\projects\VA-Strategy`
|
||||||
|
**Purpose**: Personal VA disability claims management system
|
||||||
|
**Status**: Active - in progress
|
||||||
|
**Goal**: 100% VA disability rating via TDIU
|
||||||
|
**Current Rating**: 60% combined, 30% highest single (PTSD)
|
||||||
|
|
||||||
|
**Context Files**:
|
||||||
|
- `CLAUDE.md` - Project-specific guidance for Claude Code
|
||||||
|
- `Gemini.md` - Master strategic roadmap
|
||||||
|
- `tracking/master-tracking.md` - Claim status tracker
|
||||||
|
- `tracking/immediate-action-checklist.md` - Priority to-do list
|
||||||
|
|
||||||
|
**Key Workflows**:
|
||||||
|
- Headache tracking for migraine claim (50% target)
|
||||||
|
- PTSD statement preparation (70% target)
|
||||||
|
- Sleep apnea evidence collection (50% target)
|
||||||
|
- Git-based document versioning
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. infrastructure
|
||||||
|
**Path**: `C:\Users\Fred\projects\infrastructure`
|
||||||
|
**Purpose**: Home network, Home Assistant, smart home automation
|
||||||
|
**Status**: Active maintenance + active projects
|
||||||
|
|
||||||
|
**Active Subprojects**:
|
||||||
|
- **Voice Assistant**: Local GPU-accelerated voice system (Gaming PC + Surface Go)
|
||||||
|
- **Furnace Control**: ESP32-based smart furnace controller (planning phase)
|
||||||
|
- **Home Assistant**: Main HA configuration
|
||||||
|
- **ESPHome**: Device configurations (garage controller, planned furnace)
|
||||||
|
|
||||||
|
**Context Files**:
|
||||||
|
- `README.md` - Infrastructure overview
|
||||||
|
- `docs/FURNACE-PROJECT.md` - ESP32 furnace project
|
||||||
|
- `voice-assistant/CLAUDE.md` - Voice system context
|
||||||
|
|
||||||
|
**Tech Stack**: Home Assistant, ESPHome, MQTT, Docker, Ollama, Whisper, Piper TTS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. claude-code-history
|
||||||
|
**Path**: `C:\Users\Fred\projects\claude-code-history`
|
||||||
|
**Purpose**: Session history and state persistence for Claude Code
|
||||||
|
**Status**: Background system
|
||||||
|
|
||||||
|
Contains:
|
||||||
|
- Session transcripts
|
||||||
|
- State files
|
||||||
|
- Project history
|
||||||
|
- Stats cache
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. config
|
||||||
|
**Path**: `C:\Users\Fred\projects\config`
|
||||||
|
**Purpose**: Shared configuration files
|
||||||
|
**Status**: Minimal/placeholder
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADHD Assistant Behavior
|
||||||
|
|
||||||
|
Claude Code should operate with ADHD-friendly principles when working with Fred:
|
||||||
|
|
||||||
|
### Core Principles
|
||||||
|
1. **Proactive, Not Reactive** - Notice patterns and intervene
|
||||||
|
2. **Gentle Nudging** - Suggest, don't command
|
||||||
|
3. **Celebrate Wins** - Acknowledge all completions
|
||||||
|
4. **Context Preservation** - Remember across sessions
|
||||||
|
5. **No Judgment** - Side quests are valid exploration
|
||||||
|
|
||||||
|
### Side Quest Detection
|
||||||
|
**When Fred starts working on something unrelated to the current project**, Claude should:
|
||||||
|
|
||||||
|
```
|
||||||
|
🤔 I notice we've shifted focus:
|
||||||
|
|
||||||
|
Current project: [X]
|
||||||
|
New work: [Y]
|
||||||
|
|
||||||
|
This looks like a side quest. Would you like to:
|
||||||
|
1. Continue (I'll track it)
|
||||||
|
2. Switch to the [Y] project
|
||||||
|
3. Make this a new project
|
||||||
|
4. Park it and return to [X]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Project-Specific Context Loading
|
||||||
|
When Fred opens a project in VS Code, Claude should:
|
||||||
|
1. Check for project-specific `CLAUDE.md` file
|
||||||
|
2. Load project context and current status
|
||||||
|
3. Greet with relevant session info
|
||||||
|
4. Track scope drift across project boundaries
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Shared Resources
|
||||||
|
|
||||||
|
### Claude Shared Directory
|
||||||
|
**Path**: `C:\Users\Fred\claude-shared\` (symlinked from claude-workflows)
|
||||||
|
**Contains**:
|
||||||
|
- Shared slash commands
|
||||||
|
- ADHD assistant state file (`~/.claude-assistant/state.json`)
|
||||||
|
- Setup scripts for auto-discovery
|
||||||
|
|
||||||
|
### Slash Commands (Available Everywhere)
|
||||||
|
- `/push` - Auto-commit and push
|
||||||
|
- `/eod` - End of day workflow
|
||||||
|
- `/focus` - Check current goals *(in development)*
|
||||||
|
- `/sidequest` - Log tangents *(in development)*
|
||||||
|
- `/stuck` - Get unstuck *(in development)*
|
||||||
|
- `/reflect` - Session review *(in development)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cross-Project Workflows
|
||||||
|
|
||||||
|
### When Fred Starts a Side Quest
|
||||||
|
Example: Working on VA-Strategy, starts researching ESPHome for furnace
|
||||||
|
|
||||||
|
**Claude should**:
|
||||||
|
1. Detect context shift (VA → infrastructure)
|
||||||
|
2. Offer to switch projects
|
||||||
|
3. If continuing, track as side quest in state file
|
||||||
|
4. Set timer for check-in (30 min default)
|
||||||
|
5. Preserve VA-Strategy context for return
|
||||||
|
|
||||||
|
### When Fred Opens VS Code in a Project
|
||||||
|
**Claude should**:
|
||||||
|
1. Read `.claude-context.md` (this file) for ecosystem awareness
|
||||||
|
2. Read project-specific `CLAUDE.md` if exists
|
||||||
|
3. Check state file for active session
|
||||||
|
4. Greet with context:
|
||||||
|
```
|
||||||
|
📋 Welcome back, Fred!
|
||||||
|
|
||||||
|
Project: [name]
|
||||||
|
Last session: [time ago]
|
||||||
|
Status: [brief summary]
|
||||||
|
|
||||||
|
Ready to continue?
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
|
||||||
|
### Session State File
|
||||||
|
**Location**: `~/.claude-assistant/state.json` (Windows: `C:\Users\Fred\.claude-assistant\state.json`)
|
||||||
|
|
||||||
|
**Tracks**:
|
||||||
|
- Current project and goal
|
||||||
|
- Active side quests
|
||||||
|
- Stuck signals
|
||||||
|
- Session history
|
||||||
|
- Cross-project context
|
||||||
|
|
||||||
|
### State File Schema
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"current_session": {
|
||||||
|
"project": "VA-Strategy",
|
||||||
|
"started_at": "2025-12-13T10:00:00Z",
|
||||||
|
"primary_goal": "Complete headache log entries",
|
||||||
|
"side_quests": [
|
||||||
|
{
|
||||||
|
"topic": "Research ESP32 temperature sensors",
|
||||||
|
"original_project": "VA-Strategy",
|
||||||
|
"target_project": "infrastructure",
|
||||||
|
"started_at": "2025-12-13T10:30:00Z",
|
||||||
|
"status": "in_progress"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## VS Code Insiders Setup
|
||||||
|
|
||||||
|
### Recommended Settings
|
||||||
|
To enable full context awareness in VS Code Insiders:
|
||||||
|
|
||||||
|
1. **Multi-root Workspace** - Open all projects simultaneously
|
||||||
|
2. **Workspace-Specific Settings** - Per-project `.vscode/settings.json`
|
||||||
|
3. **Claude Context Files** - This file + project-specific `CLAUDE.md` files
|
||||||
|
|
||||||
|
### Creating Multi-Root Workspace
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "C:\\Users\\Fred\\projects\\claude-workflows",
|
||||||
|
"name": "🎯 Claude Workflows"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "C:\\Users\\Fred\\projects\\VA-Strategy",
|
||||||
|
"name": "🏥 VA Strategy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "C:\\Users\\Fred\\projects\\infrastructure",
|
||||||
|
"name": "🏠 Infrastructure"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"claude.contextFiles": [
|
||||||
|
"C:\\Users\\Fred\\projects\\.claude-context.md"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Save as: `C:\Users\Fred\projects\fred-workspace.code-workspace`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Project Quick IDs
|
||||||
|
- **claude-workflows**: Productivity tools, ADHD assistant
|
||||||
|
- **VA-Strategy**: VA claims, documentation, tracking
|
||||||
|
- **infrastructure**: Home automation, voice assistant, ESPHome
|
||||||
|
- **claude-code-history**: Session history (background)
|
||||||
|
- **config**: Shared configs (minimal)
|
||||||
|
|
||||||
|
### When to Switch Projects
|
||||||
|
| You're Talking About... | Project | Action |
|
||||||
|
|------------------------|---------|--------|
|
||||||
|
| Slash commands, ADHD features | claude-workflows | Switch or side quest |
|
||||||
|
| VA claims, medical evidence | VA-Strategy | Switch or side quest |
|
||||||
|
| Home Assistant, ESP32, voice assistant | infrastructure | Switch or side quest |
|
||||||
|
| New idea unrelated to current work | TBD | Offer to create new project |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## For Claude Code: Session Start Checklist
|
||||||
|
|
||||||
|
When Fred starts VS Code Insiders:
|
||||||
|
|
||||||
|
- [ ] Read `.claude-context.md` (this file)
|
||||||
|
- [ ] Identify current project from workspace/folder
|
||||||
|
- [ ] Read project-specific `CLAUDE.md` if exists
|
||||||
|
- [ ] Check `~/.claude-assistant/state.json` for active session
|
||||||
|
- [ ] Greet with relevant context
|
||||||
|
- [ ] Be ready to detect and manage side quests
|
||||||
|
- [ ] Apply ADHD-friendly behavior from `personality.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: 2025-12-13
|
||||||
|
**Maintained By**: Fred with Claude Code assistance
|
||||||
|
**Purpose**: Provide ecosystem-level context for intelligent, ADHD-friendly Claude Code sessions
|
||||||
17
.claude/index.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /.claude/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /.claude/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href="commands/">commands/</a></li>
|
||||||
|
<li><a href="docs/">docs/</a></li>
|
||||||
|
<li><a href="settings.local.json">settings.local.json</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
15
.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Ignore embedded git repositories (manage separately)
|
||||||
|
VA-Strategy/
|
||||||
|
claude-code-history/
|
||||||
|
claude-workflows/
|
||||||
|
config/
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
Thumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# Temp files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
nul
|
||||||
|
.aider*
|
||||||
174
AIDER-QUICKSTART.md
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
# Aider Quick Start Guide
|
||||||
|
|
||||||
|
## What is Aider?
|
||||||
|
|
||||||
|
Aider is your **local, free AI coding assistant** that works with Ollama. It saves Claude API tokens for routine coding tasks.
|
||||||
|
|
||||||
|
## Setup Complete! ✓
|
||||||
|
|
||||||
|
- Aider installed
|
||||||
|
- Ollama configured with two models:
|
||||||
|
- `qwen2.5-coder:7b-instruct` (fast, 8GB VRAM)
|
||||||
|
- `qwen2.5-coder:14b-instruct` (smart, uses some RAM)
|
||||||
|
- Config file: `~/.aider.conf.yml`
|
||||||
|
- Helper functions: `C:\Users\Fred\projects\aider-helpers.ps1`
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Load Helper Functions (One Time Setup)
|
||||||
|
|
||||||
|
Add this line to your PowerShell profile:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
. C:\Users\Fred\projects\aider-helpers.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
To find your profile location:
|
||||||
|
```powershell
|
||||||
|
$PROFILE
|
||||||
|
```
|
||||||
|
|
||||||
|
Or run the helpers manually each session:
|
||||||
|
```powershell
|
||||||
|
. C:\Users\Fred\projects\aider-helpers.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Basic Usage
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Navigate to your project
|
||||||
|
cd C:\Users\Fred\projects\VA-Strategy
|
||||||
|
|
||||||
|
# Start Aider (uses 7b model by default)
|
||||||
|
aider-fast
|
||||||
|
|
||||||
|
# Or use the smarter model for complex tasks
|
||||||
|
aider-smart
|
||||||
|
|
||||||
|
# Or use plain aider (reads .aider.conf.yml)
|
||||||
|
aider
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Inside Aider
|
||||||
|
|
||||||
|
```
|
||||||
|
> Add a function to parse headache log entries
|
||||||
|
> Refactor the PTSD statement generator
|
||||||
|
> Add error handling to the tracking module
|
||||||
|
> /help # see all commands
|
||||||
|
> /exit # quit
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use What
|
||||||
|
|
||||||
|
### Use Aider (Local) for:
|
||||||
|
- ✅ Refactoring code
|
||||||
|
- ✅ Writing boilerplate
|
||||||
|
- ✅ Adding simple features
|
||||||
|
- ✅ Code reviews
|
||||||
|
- ✅ Fixing simple bugs
|
||||||
|
- ✅ Documentation
|
||||||
|
- ✅ Test writing
|
||||||
|
|
||||||
|
Cost: **$0** (completely free, uses your GPU)
|
||||||
|
|
||||||
|
### Use Claude Code for:
|
||||||
|
- 🎯 Complex architectural decisions
|
||||||
|
- 🎯 Multi-file refactors requiring deep understanding
|
||||||
|
- 🎯 Difficult debugging
|
||||||
|
- 🎯 Planning new features
|
||||||
|
- 🎯 Strategic code design
|
||||||
|
|
||||||
|
Cost: ~$3-15 per million tokens (monthly limit)
|
||||||
|
|
||||||
|
## Helper Commands
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
aider-fast # Fast 7b model (everyday coding)
|
||||||
|
aider-smart # Powerful 14b model (complex tasks)
|
||||||
|
aider-plan # Architect mode (planning)
|
||||||
|
aider-commit # Generate git commit messages
|
||||||
|
aider-watch # Auto-reload on file changes
|
||||||
|
aider-status # Show available models and commands
|
||||||
|
aider-estimate # Compare token costs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Example 1: Simple Refactor
|
||||||
|
```powershell
|
||||||
|
cd C:\Users\Fred\projects\VA-Strategy
|
||||||
|
aider-fast tracking/headache-log.py
|
||||||
|
|
||||||
|
> Refactor this to use dataclasses instead of dictionaries
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 2: Add Feature
|
||||||
|
```powershell
|
||||||
|
cd C:\Users\Fred\projects\infrastructure\voice-assistant
|
||||||
|
aider-fast
|
||||||
|
|
||||||
|
> Add a new command to check system temperature
|
||||||
|
> /add sensors.py utils.py
|
||||||
|
> Make sure it logs to the debug file
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 3: Git Commit
|
||||||
|
```powershell
|
||||||
|
git add .
|
||||||
|
aider-commit
|
||||||
|
# Aider will analyze changes and suggest a commit message
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
1. **Start Small**: Try Aider on simple tasks first to get comfortable
|
||||||
|
2. **Use Git**: Aider works best with git repos (can auto-commit)
|
||||||
|
3. **Be Specific**: Clear prompts get better results ("Add error handling for missing files" vs "make it better")
|
||||||
|
4. **Switch Models**: Use 7b for speed, 14b for quality
|
||||||
|
5. **Save Claude Tokens**: Use Aider for 80% of tasks, Claude for the 20% that need genius-level reasoning
|
||||||
|
|
||||||
|
## Token Savings Example
|
||||||
|
|
||||||
|
**Typical Day:**
|
||||||
|
- 10 simple refactors: Aider (free) instead of Claude ($0.30)
|
||||||
|
- 5 feature additions: Aider (free) instead of Claude ($0.75)
|
||||||
|
- 3 bug fixes: Aider (free) instead of Claude ($0.45)
|
||||||
|
- 2 complex architecture tasks: Claude ($0.60)
|
||||||
|
|
||||||
|
**Total saved: $1.50/day = $45/month**
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Ollama not running
|
||||||
|
```powershell
|
||||||
|
# Check Ollama status
|
||||||
|
ollama list
|
||||||
|
|
||||||
|
# Restart Ollama if needed (it should auto-start)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Model too slow
|
||||||
|
```powershell
|
||||||
|
# Switch to faster 7b model
|
||||||
|
aider --model ollama/qwen2.5-coder:7b-instruct
|
||||||
|
```
|
||||||
|
|
||||||
|
### Need better quality
|
||||||
|
```powershell
|
||||||
|
# Switch to smarter 14b model
|
||||||
|
aider --model ollama/qwen2.5-coder:14b-instruct
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Load the helper functions in your PowerShell profile
|
||||||
|
2. Try `aider-status` to verify everything works
|
||||||
|
3. Navigate to a project and run `aider-fast`
|
||||||
|
4. Start with a simple task like "Add a docstring to this function"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Happy coding!** You're now set up to save Claude tokens while maintaining productivity.
|
||||||
|
|
||||||
|
For full Aider documentation: https://aider.chat
|
||||||
190
ALERT-REDUCTION-SUMMARY.md
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
# Alert Reduction Summary
|
||||||
|
|
||||||
|
## What Changed
|
||||||
|
|
||||||
|
Your Prometheus alerting has been updated to **drastically reduce notification noise** while still catching critical issues.
|
||||||
|
|
||||||
|
### Before (Your Current Inbox)
|
||||||
|
- 🔥 **All warnings** → Email inbox spam
|
||||||
|
- Multiple alerts per minute
|
||||||
|
- Drowning out important messages
|
||||||
|
|
||||||
|
### After (New Configuration)
|
||||||
|
- ✅ **Only CRITICAL alerts** → Discord notification
|
||||||
|
- **WARNING alerts** → Logged in Prometheus UI (no notification)
|
||||||
|
- Clean inbox, important alerts still get through
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Updated Alert Thresholds
|
||||||
|
|
||||||
|
### CPU Monitoring
|
||||||
|
| Severity | Threshold | Duration | Action |
|
||||||
|
|----------|-----------|----------|--------|
|
||||||
|
| **WARNING** | 80%+ | 5 minutes | Logged only |
|
||||||
|
| **CRITICAL** | 95%+ | 5 minutes | **Discord notification** |
|
||||||
|
|
||||||
|
### Memory Monitoring
|
||||||
|
| Severity | Threshold | Duration | Action |
|
||||||
|
|----------|-----------|----------|--------|
|
||||||
|
| **WARNING** | 85%+ | 10 minutes | Logged only |
|
||||||
|
| **CRITICAL** | 95%+ | 5 minutes | **Discord notification** |
|
||||||
|
|
||||||
|
### Disk Space
|
||||||
|
| Severity | Threshold | Duration | Action |
|
||||||
|
|----------|-----------|----------|--------|
|
||||||
|
| **WARNING** | <15% free | 5 minutes | Logged only |
|
||||||
|
| **CRITICAL** | <5% free | 2 minutes | **Discord notification** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CRITICAL Alerts (Discord Notifications)
|
||||||
|
|
||||||
|
You will **ONLY** receive Discord notifications for:
|
||||||
|
|
||||||
|
### Infrastructure
|
||||||
|
- ❌ **HostDown** - Any host completely unreachable for 2+ minutes
|
||||||
|
- ❌ **ProxmoxNodeDown** - Proxmox host down for 2+ minutes
|
||||||
|
- ❌ **VPSDown** - VPS (66.63.182.168) unreachable for 2+ minutes
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- 🔥 **CriticalCPUUsage** - CPU >95% sustained for 5+ minutes
|
||||||
|
- 🧠 **CriticalMemoryUsage** - Memory >95% sustained for 5+ minutes
|
||||||
|
|
||||||
|
### Storage
|
||||||
|
- 💾 **DiskSpaceCritical** - Disk <5% free space for 2+ minutes
|
||||||
|
|
||||||
|
### Services
|
||||||
|
- 🗄️ **PostgreSQLDown** - Database down for 2+ minutes
|
||||||
|
- ⚙️ **PrometheusConfigReloadFailed** - Monitoring system config broken
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## WARNING Alerts (Logged Only)
|
||||||
|
|
||||||
|
These alerts are **visible in Prometheus UI** but **do NOT trigger notifications**:
|
||||||
|
|
||||||
|
- CPU 80-95% (warning threshold)
|
||||||
|
- Memory 85-95%
|
||||||
|
- Disk 5-15% free
|
||||||
|
- Network interface down
|
||||||
|
- High disk I/O wait
|
||||||
|
- Home Assistant down
|
||||||
|
- n8n automation down
|
||||||
|
- Prometheus scrape failures
|
||||||
|
- and more...
|
||||||
|
|
||||||
|
**You can check them anytime at:** http://10.0.10.25:9090/alerts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Quick Deploy
|
||||||
|
```bash
|
||||||
|
cd /root/.openclaw/workspace/fred-infrastructure
|
||||||
|
./deploy-reduced-alerts.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Steps (if needed)
|
||||||
|
```bash
|
||||||
|
# 1. Backup existing configs
|
||||||
|
ssh root@10.0.10.25 'mkdir -p /etc/prometheus/backups'
|
||||||
|
ssh root@10.0.10.25 'cp /etc/prometheus/alertmanager.yml /etc/prometheus/backups/alertmanager.yml.backup'
|
||||||
|
ssh root@10.0.10.25 'cp /etc/prometheus/rules/homelab-alerts.yml /etc/prometheus/backups/homelab-alerts.yml.backup'
|
||||||
|
|
||||||
|
# 2. Upload new configs
|
||||||
|
scp alertmanager-config-updated.yml root@10.0.10.25:/etc/prometheus/alertmanager.yml
|
||||||
|
scp prometheus-alert-rules-updated.yml root@10.0.10.25:/etc/prometheus/rules/homelab-alerts.yml
|
||||||
|
|
||||||
|
# 3. Reload services
|
||||||
|
ssh root@10.0.10.25 'systemctl reload prometheus'
|
||||||
|
ssh root@10.0.10.25 'systemctl reload prometheus-alertmanager'
|
||||||
|
|
||||||
|
# 4. Verify
|
||||||
|
curl http://10.0.10.25:9090/api/v1/rules
|
||||||
|
curl http://10.0.10.25:9093/api/v1/status
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Critical Alert
|
||||||
|
Send a test alert to verify Discord webhook works:
|
||||||
|
```bash
|
||||||
|
curl -X POST http://10.0.10.25:9093/api/v1/alerts -d '[
|
||||||
|
{
|
||||||
|
"labels": {
|
||||||
|
"alertname": "TestCriticalAlert",
|
||||||
|
"severity": "critical",
|
||||||
|
"instance": "test:9100"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"summary": "Test alert - please ignore"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]'
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see this appear in Discord within ~30 seconds.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Expected Results
|
||||||
|
|
||||||
|
### Your Email Inbox
|
||||||
|
- **Before:** 50+ Prometheus alerts per day
|
||||||
|
- **After:** **ZERO** (all notifications moved to Discord)
|
||||||
|
|
||||||
|
### Your Discord Server
|
||||||
|
- **Only critical issues** that need immediate attention
|
||||||
|
- Estimated: 0-2 alerts per day (unless something is actually broken)
|
||||||
|
|
||||||
|
### Prometheus UI
|
||||||
|
- **All alerts still visible** at http://10.0.10.25:9090/alerts
|
||||||
|
- Use this to check warnings when convenient
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Monitoring Your Alerts
|
||||||
|
|
||||||
|
### Check Active Alerts
|
||||||
|
```bash
|
||||||
|
# View current alerts
|
||||||
|
curl http://10.0.10.25:9090/api/v1/alerts | python3 -m json.tool
|
||||||
|
|
||||||
|
# View Alertmanager status
|
||||||
|
curl http://10.0.10.25:9093/api/v1/status | python3 -m json.tool
|
||||||
|
```
|
||||||
|
|
||||||
|
### Web Interfaces
|
||||||
|
- **Prometheus:** http://10.0.10.25:9090/alerts
|
||||||
|
- **Alertmanager:** http://10.0.10.25:9093/#/alerts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollback (If Needed)
|
||||||
|
|
||||||
|
If something goes wrong, restore the backup:
|
||||||
|
```bash
|
||||||
|
ssh root@10.0.10.25 'cp /etc/prometheus/backups/alertmanager.yml.* /etc/prometheus/alertmanager.yml'
|
||||||
|
ssh root@10.0.10.25 'cp /etc/prometheus/backups/homelab-alerts.yml.* /etc/prometheus/rules/homelab-alerts.yml'
|
||||||
|
ssh root@10.0.10.25 'systemctl reload prometheus prometheus-alertmanager'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Created
|
||||||
|
|
||||||
|
- `prometheus-alert-rules-updated.yml` - Updated alert rules (CPU 80%+, warnings logged)
|
||||||
|
- `alertmanager-config-updated.yml` - Only critical → Discord, warnings → null
|
||||||
|
- `deploy-reduced-alerts.sh` - Automated deployment script
|
||||||
|
- `ALERT-REDUCTION-SUMMARY.md` - This file
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
- **"I want to see warnings occasionally"** → Check http://10.0.10.25:9090/alerts daily
|
||||||
|
- **"I need email back"** → We can add it back for critical-only
|
||||||
|
- **"80% CPU is too low"** → We can adjust the threshold up/down
|
||||||
|
- **"I want alerts in Slack instead"** → Easy to add another webhook
|
||||||
|
|
||||||
|
Let me know if you want to tweak anything! 🎯
|
||||||
59
CLAUDE.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
This repository manages Fred's homelab infrastructure - a mix of deployment scripts, monitoring tools, and service integrations for a Proxmox-based network.
|
||||||
|
|
||||||
|
**When you need specific information, refer to these specialized docs:**
|
||||||
|
|
||||||
|
### Core Architecture
|
||||||
|
|
||||||
|
- [.claude/docs/ARCHITECTURE.md](.claude/docs/ARCHITECTURE.md) - Infrastructure components, network topology, WireGuard VPN
|
||||||
|
- [.claude/docs/SERVICES.md](.claude/docs/SERVICES.md) - Deployed services, container IDs, service-specific details
|
||||||
|
- [.claude/docs/SCRIPTS.md](.claude/docs/SCRIPTS.md) - Deployment scripts and automation tools
|
||||||
|
|
||||||
|
### Operations
|
||||||
|
|
||||||
|
- [.claude/docs/COMMON-TASKS.md](.claude/docs/COMMON-TASKS.md) - SSH access, Caddy management, container operations
|
||||||
|
- [.claude/docs/CERTIFICATES.md](.claude/docs/CERTIFICATES.md) - Step-CA setup, ACME provisioner, SSL configuration
|
||||||
|
|
||||||
|
### Detailed References (load when needed)
|
||||||
|
|
||||||
|
- Subdirectories have their own CLAUDE.md files (infrastructure/, n8n-workflows/, etc.)
|
||||||
|
- Service-specific deployment guides in respective directories
|
||||||
|
- Submodule documentation in submodule directories
|
||||||
|
|
||||||
|
## Essential Quick Facts
|
||||||
|
|
||||||
|
**Primary Infrastructure:**
|
||||||
|
|
||||||
|
- VPS: fred@66.63.182.168 (vps.nianticbooks.com) - Caddy reverse proxy
|
||||||
|
- Proxmox: root@10.0.10.3 (main-pve) - LXC container host
|
||||||
|
- Network: 10.0.10.0/24 (WireGuard tunnel: 10.0.8.0/24)
|
||||||
|
|
||||||
|
**Critical Services:**
|
||||||
|
|
||||||
|
- Uptime Kuma: 10.0.10.26 (CT 128)
|
||||||
|
- Home Assistant: 10.0.10.24
|
||||||
|
- CA Server: 10.0.10.15 (CT 115)
|
||||||
|
|
||||||
|
## Repository Structure
|
||||||
|
|
||||||
|
**Subdirectories:**
|
||||||
|
|
||||||
|
- `infrastructure/` - Core infrastructure docs and automation
|
||||||
|
- `n8n-workflows/` - n8n workflow JSON definitions
|
||||||
|
- `bible-reading-plan/` - Submodule (separate repo)
|
||||||
|
|
||||||
|
**Slash Commands:**
|
||||||
|
|
||||||
|
- `/eod`, `/push`, `/focus`, `/sidequest`, `/stuck`, `/reflect` (in `.claude/commands/`)
|
||||||
|
|
||||||
|
## Key Constraints
|
||||||
|
|
||||||
|
- Proxmox storage: `local` only (never `local-lvm`)
|
||||||
|
- SSH: Always use key-based auth
|
||||||
|
- Test on target system before committing
|
||||||
|
- VPS: 2 CPU / 4GB RAM - lightweight services only
|
||||||
11
CUsersFredDownloadscaddy-internal-root-ca.crt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBozCCAUmgAwIBAgIQDhQK0OV98/81eUmxrmQDODAKBggqhkjOPQQDAjAwMS4w
|
||||||
|
LAYDVQQDEyVDYWRkeSBMb2NhbCBBdXRob3JpdHkgLSAyMDI2IEVDQyBSb290MB4X
|
||||||
|
DTI2MDEyNTE5Mjg0MloXDTM1MTIwNDE5Mjg0MlowMDEuMCwGA1UEAxMlQ2FkZHkg
|
||||||
|
TG9jYWwgQXV0aG9yaXR5IC0gMjAyNiBFQ0MgUm9vdDBZMBMGByqGSM49AgEGCCqG
|
||||||
|
SM49AwEHA0IABK+03Zxz5H6c6/5NPoRsDbR50mSiYWitxjSgu8WVHi9QKrhRUDcP
|
||||||
|
9xulfLgnDEGKzg6PHP8uSKmKjJ+S94c633+jRTBDMA4GA1UdDwEB/wQEAwIBBjAS
|
||||||
|
BgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBTa7eOocuWd3U3qq0Upi55X/VrC
|
||||||
|
PjAKBggqhkjOPQQDAgNIADBFAiA0aW0FfQ3KRZ5ojp3oK5wwBSmjaRNQko7qtXw1
|
||||||
|
PZrlEgIhANpvOrrYzoBCEm/anqkOmTL3qf+wFNFyg+hAl3L70O/f
|
||||||
|
-----END CERTIFICATE-----
|
||||||
138
DEPLOY-NOW.md
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
# 🚀 DEPLOY NOW - Stop the Alert Spam!
|
||||||
|
|
||||||
|
## Quick Deploy (Copy/Paste This)
|
||||||
|
|
||||||
|
Run these commands from **your terminal** (PC, not OpenClaw):
|
||||||
|
|
||||||
|
### 1. Access the Prometheus container
|
||||||
|
```bash
|
||||||
|
# SSH to Proxmox
|
||||||
|
ssh root@10.0.10.3
|
||||||
|
|
||||||
|
# Enter the Prometheus container
|
||||||
|
pct enter 125
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Backup existing configs
|
||||||
|
```bash
|
||||||
|
# Inside the container
|
||||||
|
mkdir -p /etc/prometheus/backups
|
||||||
|
cp /etc/prometheus/alertmanager.yml /etc/prometheus/backups/alertmanager.yml.backup
|
||||||
|
cp /etc/prometheus/rules/homelab-alerts.yml /etc/prometheus/backups/homelab-alerts.yml.backup
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Download the new configs
|
||||||
|
From your **PC**, download the updated files I created:
|
||||||
|
```bash
|
||||||
|
# On your PC
|
||||||
|
scp root@10.0.10.28:/root/.openclaw/workspace/fred-infrastructure/alertmanager-config-updated.yml ~/
|
||||||
|
scp root@10.0.10.28:/root/.openclaw/workspace/fred-infrastructure/prometheus-alert-rules-updated.yml ~/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Upload to Prometheus container
|
||||||
|
```bash
|
||||||
|
# On your PC
|
||||||
|
scp ~/alertmanager-config-updated.yml root@10.0.10.3:/tmp/
|
||||||
|
scp ~/prometheus-alert-rules-updated.yml root@10.0.10.3:/tmp/
|
||||||
|
```
|
||||||
|
|
||||||
|
Then back in Proxmox SSH:
|
||||||
|
```bash
|
||||||
|
# Copy into container
|
||||||
|
pct push 125 /tmp/alertmanager-config-updated.yml /etc/prometheus/alertmanager.yml
|
||||||
|
pct push 125 /tmp/prometheus-alert-rules-updated.yml /etc/prometheus/rules/homelab-alerts.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Reload Prometheus
|
||||||
|
```bash
|
||||||
|
# Inside the container (pct enter 125)
|
||||||
|
systemctl reload prometheus
|
||||||
|
systemctl reload prometheus-alertmanager
|
||||||
|
|
||||||
|
# Verify services reloaded
|
||||||
|
systemctl status prometheus
|
||||||
|
systemctl status prometheus-alertmanager
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Test it works
|
||||||
|
```bash
|
||||||
|
# Should see the new config
|
||||||
|
curl http://10.0.10.25:9090/api/v1/rules
|
||||||
|
|
||||||
|
# Send test alert to Discord
|
||||||
|
curl -X POST http://10.0.10.25:9093/api/v1/alerts -d '[
|
||||||
|
{
|
||||||
|
"labels": {
|
||||||
|
"alertname": "TestCriticalAlert",
|
||||||
|
"severity": "critical",
|
||||||
|
"instance": "test:9100"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"summary": "Test alert - please ignore"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]'
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see the test alert appear in **Discord** within 30 seconds!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ EVEN FASTER: One-Liner (Advanced)
|
||||||
|
|
||||||
|
If you want to do it all in one go:
|
||||||
|
```bash
|
||||||
|
ssh root@10.0.10.3 "pct exec 125 -- bash -c '
|
||||||
|
mkdir -p /etc/prometheus/backups && \
|
||||||
|
cp /etc/prometheus/alertmanager.yml /etc/prometheus/backups/alertmanager.yml.backup && \
|
||||||
|
cp /etc/prometheus/rules/homelab-alerts.yml /etc/prometheus/backups/homelab-alerts.yml.backup && \
|
||||||
|
curl -o /etc/prometheus/alertmanager.yml http://10.0.10.28:PORT/workspace/fred-infrastructure/alertmanager-config-updated.yml && \
|
||||||
|
curl -o /etc/prometheus/rules/homelab-alerts.yml http://10.0.10.28:PORT/workspace/fred-infrastructure/prometheus-alert-rules-updated.yml && \
|
||||||
|
systemctl reload prometheus prometheus-alertmanager
|
||||||
|
'"
|
||||||
|
```
|
||||||
|
|
||||||
|
(Replace `PORT` with OpenClaw web port if you have file serving enabled)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 What This Does
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
- ⚠️ WARNING alerts every 2 minutes → Email
|
||||||
|
- "CPU changed 61.5% rapidly (5.04 → 1.94)" ← WTF is this even
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
- ✅ CRITICAL alerts only → Discord
|
||||||
|
- ❌ WARNING alerts → Logged, not sent
|
||||||
|
- 📧 Email inbox → CLEAN
|
||||||
|
|
||||||
|
**Result:**
|
||||||
|
- Your inbox goes from 50+ alerts/day to ZERO
|
||||||
|
- Discord gets only real emergencies (host down, disk full, etc.)
|
||||||
|
- Warnings still logged in Prometheus UI if you want to check
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 Current Alert Spam Example
|
||||||
|
|
||||||
|
That alert you just got:
|
||||||
|
```
|
||||||
|
⚠️ WARNING: 10.0.10.3:9100
|
||||||
|
cpu_usage on 10.0.10.3:9100 changed 61.5% rapidly (5.04 → 1.94)
|
||||||
|
```
|
||||||
|
|
||||||
|
This is **literally complaining that CPU usage went DOWN**. Pure noise.
|
||||||
|
|
||||||
|
After deployment: **This will NEVER notify you again.** It'll be logged if you want to see it, but won't spam your inbox.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
- **"I can't SSH to Proxmox"** → Let me know, I'll help
|
||||||
|
- **"It broke something"** → Restore backup: `cp /etc/prometheus/backups/* /etc/prometheus/`
|
||||||
|
- **"I want it even quieter"** → We can tune thresholds further
|
||||||
|
- **"I want email back for criticals"** → Easy to add
|
||||||
|
|
||||||
|
**Just deploy this and watch your inbox become peaceful again.** 🧘♂️✨
|
||||||
317
N8N-UPTIME-KUMA-GUIDE.md
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
# n8n + Uptime Kuma Integration Guide
|
||||||
|
|
||||||
|
**n8n for Beginners:** This guide will walk you through setting up smart notifications for your infrastructure monitoring.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What This Does
|
||||||
|
|
||||||
|
This n8n workflow receives alerts from Uptime Kuma and routes them intelligently:
|
||||||
|
|
||||||
|
- **CRITICAL alerts** → Email + Discord/Slack (immediate)
|
||||||
|
- **PUBLIC alerts** → Discord (after retry threshold)
|
||||||
|
- **INTERNAL/METRICS/UTILITY** → Logged only (no spam)
|
||||||
|
|
||||||
|
All alerts are logged for debugging and historical tracking.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Import Workflow into n8n
|
||||||
|
|
||||||
|
### 1.1 Access n8n
|
||||||
|
Go to http://10.0.10.22:5678
|
||||||
|
|
||||||
|
### 1.2 Import the Workflow
|
||||||
|
1. Click **Workflows** (left sidebar)
|
||||||
|
2. Click **Add Workflow** (top right)
|
||||||
|
3. Click the **⋮** menu (top right) → **Import from File**
|
||||||
|
4. Select: `C:\Users\Fred\projects\n8n-uptime-kuma-workflow.json`
|
||||||
|
5. Click **Import**
|
||||||
|
|
||||||
|
### 1.3 Activate the Workflow
|
||||||
|
1. The workflow editor will open
|
||||||
|
2. Toggle the **Active** switch (top right) to **ON**
|
||||||
|
3. You should see "Workflow activated" message
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Get Your Webhook URL
|
||||||
|
|
||||||
|
### 2.1 Find the Webhook URL
|
||||||
|
1. In the workflow editor, click on the **"Webhook - Uptime Kuma"** node (first blue node)
|
||||||
|
2. Look for **Test URL** or **Production URL**
|
||||||
|
3. Copy the production URL - it should look like:
|
||||||
|
```
|
||||||
|
http://10.0.10.22:5678/webhook/uptime-kuma
|
||||||
|
```
|
||||||
|
|
||||||
|
**Save this URL** - you'll need it for Uptime Kuma!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Configure Uptime Kuma Notifications
|
||||||
|
|
||||||
|
### 3.1 Create n8n Notification Channel
|
||||||
|
1. Go to Uptime Kuma: http://10.0.10.26:3001
|
||||||
|
2. Click **Settings** (left sidebar)
|
||||||
|
3. Click **Notifications** tab
|
||||||
|
4. Click **Add New Notification**
|
||||||
|
|
||||||
|
### 3.2 Configure Webhook
|
||||||
|
- **Notification Type:** Select **Webhook**
|
||||||
|
- **Friendly Name:** `n8n Alert Router`
|
||||||
|
- **POST URL:** `http://10.0.10.22:5678/webhook/uptime-kuma`
|
||||||
|
- **Content Type:** `application/json`
|
||||||
|
- **Notification Sound:** (your preference)
|
||||||
|
- Click **Test** to verify
|
||||||
|
- Click **Save**
|
||||||
|
|
||||||
|
### 3.3 Assign to Monitors
|
||||||
|
Now assign this notification to your monitors:
|
||||||
|
|
||||||
|
1. Go to **Dashboard**
|
||||||
|
2. Click on a **CRITICAL** monitor (e.g., "Proxmox - main-pve")
|
||||||
|
3. Click **Edit**
|
||||||
|
4. Scroll to **Notifications**
|
||||||
|
5. Select **n8n Alert Router**
|
||||||
|
6. **Apply on:** Select **When status is DOWN**
|
||||||
|
7. Click **Save**
|
||||||
|
|
||||||
|
**Repeat for all CRITICAL monitors** (you can select multiple monitors and edit in bulk)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Enable Notification Channels
|
||||||
|
|
||||||
|
By default, all notification nodes are **disabled** (to prevent errors). Enable the ones you want to use:
|
||||||
|
|
||||||
|
### Option A: Discord (Recommended for Beginners)
|
||||||
|
|
||||||
|
**4.1 Create Discord Webhook:**
|
||||||
|
1. Open Discord
|
||||||
|
2. Go to your server → Settings → Integrations
|
||||||
|
3. Click **Webhooks** → **New Webhook**
|
||||||
|
4. Name it "Uptime Kuma Alerts"
|
||||||
|
5. Select channel (e.g., #monitoring)
|
||||||
|
6. Copy the **Webhook URL**
|
||||||
|
|
||||||
|
**4.2 Configure in n8n:**
|
||||||
|
1. In the workflow, click **"Discord - CRITICAL Alert"** node
|
||||||
|
2. Click **Parameters**
|
||||||
|
3. Paste your Discord webhook URL
|
||||||
|
4. Toggle the node **Enabled** (remove checkmark from "Disabled")
|
||||||
|
5. Click **Save** (top right)
|
||||||
|
|
||||||
|
**Repeat for "Discord - PUBLIC Alert"** if you want public service alerts too.
|
||||||
|
|
||||||
|
### Option B: Email (SMTP)
|
||||||
|
|
||||||
|
**4.1 Configure in n8n:**
|
||||||
|
1. Click **"Email - CRITICAL Alert"** node
|
||||||
|
2. Click **Credentials** → **Create New**
|
||||||
|
3. Choose your email provider:
|
||||||
|
- **Gmail:** Use App Password (not your main password)
|
||||||
|
- **Outlook/Office365:** Use SMTP settings
|
||||||
|
- **Custom SMTP:** Enter your SMTP details
|
||||||
|
|
||||||
|
**Example: Gmail Setup**
|
||||||
|
- **From Email:** your-gmail@gmail.com
|
||||||
|
- **To Email:** your-alert-email@gmail.com
|
||||||
|
- **SMTP Host:** smtp.gmail.com
|
||||||
|
- **SMTP Port:** 587
|
||||||
|
- **Username:** your-gmail@gmail.com
|
||||||
|
- **Password:** (App Password from Google Account settings)
|
||||||
|
- **Secure:** TLS
|
||||||
|
|
||||||
|
4. Click **Test** to verify
|
||||||
|
5. Click **Save**
|
||||||
|
6. Enable the node (uncheck "Disabled")
|
||||||
|
|
||||||
|
### Option C: Slack
|
||||||
|
|
||||||
|
**4.1 Create Slack App:**
|
||||||
|
1. Go to https://api.slack.com/apps
|
||||||
|
2. Click **Create New App** → **From scratch**
|
||||||
|
3. Name: "Uptime Kuma"
|
||||||
|
4. Select your workspace
|
||||||
|
5. Go to **OAuth & Permissions**
|
||||||
|
6. Add Bot Token Scopes: `chat:write`, `chat:write.public`
|
||||||
|
7. Click **Install to Workspace**
|
||||||
|
8. Copy the **Bot User OAuth Token**
|
||||||
|
|
||||||
|
**4.2 Configure in n8n:**
|
||||||
|
1. Click **"Slack - CRITICAL Alert"** node
|
||||||
|
2. Click **Credentials** → **Create New**
|
||||||
|
3. Paste your Bot Token
|
||||||
|
4. Click **Save**
|
||||||
|
5. Enable the node
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Test Your Setup
|
||||||
|
|
||||||
|
### 5.1 Trigger a Test Alert
|
||||||
|
Let's test with a non-critical monitor:
|
||||||
|
|
||||||
|
1. Go to Uptime Kuma
|
||||||
|
2. Find **"[UTILITY] CA Server"** monitor
|
||||||
|
3. Click **Edit**
|
||||||
|
4. Temporarily change port from `8443` to `9999` (invalid port)
|
||||||
|
5. Click **Save**
|
||||||
|
6. Wait 30-60 seconds for the monitor to check
|
||||||
|
|
||||||
|
### 5.2 Check n8n Execution
|
||||||
|
1. Go to n8n: http://10.0.10.22:5678
|
||||||
|
2. Click **Executions** (left sidebar)
|
||||||
|
3. You should see a new execution
|
||||||
|
4. Click it to view details
|
||||||
|
|
||||||
|
### 5.3 Verify Alert Received
|
||||||
|
- **Discord:** Check your #monitoring channel
|
||||||
|
- **Email:** Check your inbox
|
||||||
|
- **Slack:** Check your channel
|
||||||
|
|
||||||
|
### 5.4 Fix the Monitor
|
||||||
|
1. Go back to Uptime Kuma
|
||||||
|
2. Edit the CA Server monitor
|
||||||
|
3. Change port back to `8443`
|
||||||
|
4. Save
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workflow Overview
|
||||||
|
|
||||||
|
Here's what happens when a monitor goes down:
|
||||||
|
|
||||||
|
```
|
||||||
|
Uptime Kuma Alert
|
||||||
|
↓
|
||||||
|
n8n Webhook Receives
|
||||||
|
↓
|
||||||
|
Format Alert Data
|
||||||
|
(Parses monitor info, status, error)
|
||||||
|
↓
|
||||||
|
┌──────────────┬──────────────┐
|
||||||
|
↓ ↓ ↓
|
||||||
|
Is CRITICAL? Is PUBLIC? Log All
|
||||||
|
↓ ↓ Alerts
|
||||||
|
↓ ↓
|
||||||
|
Email + Discord
|
||||||
|
Discord + (Optional)
|
||||||
|
Slack
|
||||||
|
(Optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Smart Routing:**
|
||||||
|
- CRITICAL monitors (VPS, Proxmox, PostgreSQL) → Immediate multi-channel alerts
|
||||||
|
- PUBLIC monitors (websites) → Discord only
|
||||||
|
- Everything else → Logged for review
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Customization Ideas
|
||||||
|
|
||||||
|
### Add SMS Alerts (via Twilio)
|
||||||
|
1. Add **Twilio** node after "Is CRITICAL?"
|
||||||
|
2. Configure with your Twilio account
|
||||||
|
3. Send SMS for CRITICAL alerts only
|
||||||
|
|
||||||
|
### Add Phone Call Alerts
|
||||||
|
1. Use **Twilio** or **VoIP.ms** node
|
||||||
|
2. Trigger phone calls for critical infrastructure failures
|
||||||
|
3. Only for CRITICAL monitors to avoid spam
|
||||||
|
|
||||||
|
### Create Alert Batching
|
||||||
|
1. Add **Wait** node before Discord/Email
|
||||||
|
2. Collect multiple alerts for 5 minutes
|
||||||
|
3. Send one consolidated message
|
||||||
|
|
||||||
|
### Add Status Page Integration
|
||||||
|
1. After "Format Alert Data", add **HTTP Request** node
|
||||||
|
2. POST to your status page API
|
||||||
|
3. Auto-update public status page
|
||||||
|
|
||||||
|
### Store Alerts in Database
|
||||||
|
1. Add **PostgreSQL** node (connect to 10.0.10.20)
|
||||||
|
2. Create `alerts` table
|
||||||
|
3. Log all alerts for historical analysis
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Webhook Not Receiving Data
|
||||||
|
- Check workflow is **Active** (toggle at top right)
|
||||||
|
- Verify webhook URL is correct in Uptime Kuma
|
||||||
|
- Check n8n executions for errors
|
||||||
|
|
||||||
|
### Discord/Email Not Sending
|
||||||
|
- Make sure node is **Enabled** (not grayed out)
|
||||||
|
- Check credentials are configured
|
||||||
|
- Look at n8n execution details for error messages
|
||||||
|
|
||||||
|
### Too Many Alerts
|
||||||
|
- Increase retry counts in Uptime Kuma monitors
|
||||||
|
- Change "Apply on" to only DOWN (not also UP)
|
||||||
|
- Add filtering logic in n8n (only alert after X failures)
|
||||||
|
|
||||||
|
### Not Enough Alerts
|
||||||
|
- Check monitor has notification assigned
|
||||||
|
- Verify "Apply on" includes DOWN status
|
||||||
|
- Check n8n workflow is active
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Advanced: Alert Suppression (Prevent Spam)
|
||||||
|
|
||||||
|
Add this after "Format Alert Data" to suppress duplicate alerts:
|
||||||
|
|
||||||
|
1. Add **Function** node
|
||||||
|
2. Code:
|
||||||
|
```javascript
|
||||||
|
// Suppress duplicate alerts within 15 minutes
|
||||||
|
const alertKey = `${$json.fullName}-${$json.status}`;
|
||||||
|
const lastAlert = $workflow.staticData[alertKey] || 0;
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
if (now - lastAlert < 15 * 60 * 1000) {
|
||||||
|
// Alert sent less than 15 min ago - suppress
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send alert and remember timestamp
|
||||||
|
$workflow.staticData[alertKey] = now;
|
||||||
|
return [$input.item];
|
||||||
|
```
|
||||||
|
|
||||||
|
This prevents getting the same alert repeatedly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. ✅ Import workflow into n8n
|
||||||
|
2. ✅ Activate workflow and get webhook URL
|
||||||
|
3. ✅ Configure Uptime Kuma notification
|
||||||
|
4. ✅ Enable Discord/Email/Slack notifications
|
||||||
|
5. ✅ Test with a non-critical monitor
|
||||||
|
6. ✅ Assign notifications to all CRITICAL monitors
|
||||||
|
7. ⏸️ Customize alert routing (optional)
|
||||||
|
8. ⏸️ Add SMS/phone call alerts (optional)
|
||||||
|
9. ⏸️ Create alert dashboard in Grafana (optional)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**n8n Dashboard:** http://10.0.10.22:5678
|
||||||
|
**Uptime Kuma:** http://10.0.10.26:3001
|
||||||
|
**Workflow File:** `C:\Users\Fred\projects\n8n-uptime-kuma-workflow.json`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Pro Tip:** Once you're comfortable with n8n, you can create workflows for:
|
||||||
|
- Auto-restarting failed services
|
||||||
|
- Creating GitHub issues when monitors fail
|
||||||
|
- Sending daily/weekly uptime reports
|
||||||
|
- Integration with your homelab automation
|
||||||
|
|
||||||
|
Welcome to n8n! 🎉
|
||||||
15
Printing/.claude/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /Printing/.claude/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /Printing/.claude/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href="settings.local.json">settings.local.json</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
27
Printing/.claude/settings.local.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(python generate_nameplates.py:*)",
|
||||||
|
"Bash(git add:*)",
|
||||||
|
"Bash(git init:*)",
|
||||||
|
"Bash(git remote add:*)",
|
||||||
|
"Bash(git fetch:*)",
|
||||||
|
"Bash(git commit:*)",
|
||||||
|
"Bash(git push:*)",
|
||||||
|
"Bash(git ls-remote:*)",
|
||||||
|
"Bash(done)",
|
||||||
|
"Bash(git pull:*)",
|
||||||
|
"Bash(python:*)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D 'name=\"\"Fred\"\"' -o \"TEST_Fred_raised_text.stl\" \"zipper_pull_raised_text_template.scad\")",
|
||||||
|
"Bash(ls:*)",
|
||||||
|
"Bash(head:*)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" \"zipper-pulls-raised-text/Ford-Fred (2).stl\" --export-format echo --o -)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" \"zipper-pulls-raised-text/111.stl\" --export-format echo --o -)",
|
||||||
|
"Bash(dir \"C:\\Users\\Fred\\.claude-worktrees\\Printing\\eager-bose\\Nameplates\\zipper-pulls-raised-text\" /T:W)",
|
||||||
|
"Bash(findstr:*)",
|
||||||
|
"Bash(powershell:*)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
41
Printing/.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Generated STL files
|
||||||
|
*.stl
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Python cache
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
desktop.ini
|
||||||
|
|
||||||
|
# Node.js (if accidentally included)
|
||||||
|
node_modules/
|
||||||
|
*.tar.xz
|
||||||
|
*.tar.gz
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*~
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
test_output.stl
|
||||||
453
Printing/CLAUDE.md
Normal file
@@ -0,0 +1,453 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
A collection of 3D printing utilities for generating personalized STL files using **Python + OpenSCAD**. The repository contains two main subprojects:
|
||||||
|
|
||||||
|
1. **Nameplates** - Batch nameplate and zipper pull generator
|
||||||
|
2. **Key caps** - Mechanical keyboard keycap scaling utilities
|
||||||
|
|
||||||
|
All projects use OpenSCAD as the 3D modeling engine, invoked via Python scripts or standalone SCAD files.
|
||||||
|
|
||||||
|
## Repository Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
Printing/
|
||||||
|
├── Nameplates/ # Nameplate and zipper pull generators
|
||||||
|
│ ├── generate_nameplates.py # Batch nameplate generator
|
||||||
|
│ ├── generate_zipper_pulls.py # Batch zipper pull generator
|
||||||
|
│ ├── nameplate_template.scad # Nameplate template (38mm height, no hole)
|
||||||
|
│ ├── zipper_pull_template.scad # Zipper pull template (34mm height, 4mm hole)
|
||||||
|
│ ├── names.txt # Input names (one per line)
|
||||||
|
│ ├── output_stl/ # Generated nameplate STLs
|
||||||
|
│ ├── zipper-pulls/ # Generated zipper pull STLs
|
||||||
|
│ ├── Fordscript.ttf # Custom cursive font (must be installed)
|
||||||
|
│ └── CLAUDE.md # Detailed nameplate documentation
|
||||||
|
│
|
||||||
|
└── Key caps/ # Keyboard keycap scaling
|
||||||
|
├── Body5_scaled_FIXED.scad # Primary keycap scaling script
|
||||||
|
├── Body5.stl # Original keycap model
|
||||||
|
├── CLAUDE.md # Detailed keycap documentation
|
||||||
|
└── README_scaling.md # User guide for scaling workflow
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical Dependencies
|
||||||
|
|
||||||
|
### OpenSCAD Installation
|
||||||
|
- **Path**: `C:\Program Files\OpenSCAD\openscad.exe` (hardcoded in Python scripts)
|
||||||
|
- **Download**: https://openscad.org/downloads.html
|
||||||
|
- **Update path** in scripts if installed elsewhere (see `generate_nameplates.py:36`, `generate_zipper_pulls.py:36`)
|
||||||
|
|
||||||
|
### Font Requirements (Nameplates/Zipper Pulls Only)
|
||||||
|
- **Font**: Fordscript cursive font (`Nameplates/Fordscript.ttf`)
|
||||||
|
- **Installation**: Must be installed in Windows (right-click → Install for all users)
|
||||||
|
- **Critical**: OpenSCAD references fonts by system name, not file path
|
||||||
|
- **Verification**: After install, restart OpenSCAD before generating files
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
### Nameplates
|
||||||
|
```bash
|
||||||
|
# Generate all nameplates (38mm height, no hole)
|
||||||
|
cd Nameplates
|
||||||
|
python generate_nameplates.py
|
||||||
|
|
||||||
|
# Output: output_stl/*.stl
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zipper Pulls
|
||||||
|
```bash
|
||||||
|
# Generate all zipper pulls (100mm × 34mm total, 4mm hole with 1mm clearance)
|
||||||
|
cd Nameplates
|
||||||
|
python generate_zipper_pulls.py
|
||||||
|
|
||||||
|
# Output: zipper-pulls/*.stl
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Caps
|
||||||
|
```powershell
|
||||||
|
# Render scaled keycap (command line)
|
||||||
|
& "C:\Program Files\OpenSCAD\openscad.exe" -o "output.stl" "Body5_scaled_FIXED.scad"
|
||||||
|
|
||||||
|
# Or: Open in OpenSCAD GUI, adjust parameters, press F6 to render, export STL
|
||||||
|
& "C:\Program Files\OpenSCAD\openscad.exe" "Body5_scaled_FIXED.scad"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual OpenSCAD Testing
|
||||||
|
```powershell
|
||||||
|
# Test single nameplate
|
||||||
|
& "C:\Program Files\OpenSCAD\openscad.exe" -D 'name="TestName"' -o test_output.stl nameplate_template.scad
|
||||||
|
|
||||||
|
# Test single zipper pull
|
||||||
|
& "C:\Program Files\OpenSCAD\openscad.exe" -D 'name="TestName"' -o test_output.stl zipper_pull_template.scad
|
||||||
|
|
||||||
|
# Preview in GUI (edit parameters at top of file)
|
||||||
|
& "C:\Program Files\OpenSCAD\openscad.exe" nameplate_template.scad
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Nameplates vs Zipper Pulls
|
||||||
|
|
||||||
|
Both use the same two-layer oval design (white base + blue top with engraved text) but differ in dimensions and features:
|
||||||
|
|
||||||
|
| Feature | Nameplates | Zipper Pulls |
|
||||||
|
|---------|-----------|-------------|
|
||||||
|
| Template | `nameplate_template.scad` | `zipper_pull_template.scad` |
|
||||||
|
| Generator | `generate_nameplates.py` | `generate_zipper_pulls.py` |
|
||||||
|
| Total Size (W×H) | 100mm × 38mm | 100mm × 34mm |
|
||||||
|
| Blue Layer Size | 96mm × 34mm | 96mm × 30mm |
|
||||||
|
| White Base Offset | 2mm all sides | 2mm all sides |
|
||||||
|
| Font Size | 30mm | 13mm |
|
||||||
|
| Hole | None | 4mm diameter, left side |
|
||||||
|
| Hole Clearance | N/A | 1mm from white base edge |
|
||||||
|
| Output Directory | `output_stl/` | `zipper-pulls/` |
|
||||||
|
| Use Case | Display nameplates | Zipper/bag attachments |
|
||||||
|
|
||||||
|
**Important**: These are separate templates and scripts. Modifying one does not affect the other.
|
||||||
|
|
||||||
|
### Pipeline Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
names.txt
|
||||||
|
↓
|
||||||
|
Python Generator Script (reads names, iterates)
|
||||||
|
↓
|
||||||
|
OpenSCAD CLI (renders each name via template)
|
||||||
|
↓
|
||||||
|
STL Files (output to respective directories)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Pattern**: Python script calls OpenSCAD subprocess for each name:
|
||||||
|
```python
|
||||||
|
cmd = [
|
||||||
|
r'C:\Program Files\OpenSCAD\openscad.exe',
|
||||||
|
'-D', f'name="{escaped_name}"', # Override parameter
|
||||||
|
'-o', output_file, # Output STL path
|
||||||
|
template_file # SCAD template
|
||||||
|
]
|
||||||
|
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
### OpenSCAD Template Pattern
|
||||||
|
|
||||||
|
All SCAD templates follow this structure:
|
||||||
|
```scad
|
||||||
|
// 1. Parameters (overridable via -D flag)
|
||||||
|
name = "NAME";
|
||||||
|
oval_width = 100;
|
||||||
|
// ... more parameters
|
||||||
|
|
||||||
|
// 2. Modules (reusable geometry functions)
|
||||||
|
module oval(width, height, depth) { ... }
|
||||||
|
module base_layer() { ... }
|
||||||
|
module top_layer() { ... }
|
||||||
|
|
||||||
|
// 3. Assembly module
|
||||||
|
module nameplate() {
|
||||||
|
base_layer();
|
||||||
|
top_layer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Execution (builds the model)
|
||||||
|
nameplate();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project-Specific Conventions
|
||||||
|
|
||||||
|
### Name Sanitization
|
||||||
|
Python scripts convert names to safe filenames:
|
||||||
|
- Removes special characters (keeps alphanumeric, spaces, hyphens, underscores)
|
||||||
|
- Replaces spaces with underscores
|
||||||
|
- Escapes quotes for shell: `name.replace('"', '\\"')`
|
||||||
|
- Example: `"John Doe"` → `John_Doe.stl`
|
||||||
|
|
||||||
|
### Two-Layer Design Philosophy
|
||||||
|
- **White base layer**: Structural foundation, extends beyond blue layer by `base_offset` (2mm)
|
||||||
|
- **Blue top layer**: Visual layer with engraved text
|
||||||
|
- **Text engraving**: Cuts completely through blue layer (`text_depth = top_thickness = 1mm`) to expose white base underneath
|
||||||
|
- **Result**: White text on blue background
|
||||||
|
|
||||||
|
### Hole Positioning (Zipper Pulls)
|
||||||
|
Hole is positioned on left side with clearance from white base edge:
|
||||||
|
```scad
|
||||||
|
white_width = oval_width + base_offset*2;
|
||||||
|
hole_x = -(white_width/2) + (hole_diameter/2) + hole_clearance;
|
||||||
|
// For 96mm blue + 2mm offset = 100mm white, 4mm hole, 1mm clearance:
|
||||||
|
// hole_x = -50 + 2 + 1 = -47mm from center
|
||||||
|
```
|
||||||
|
|
||||||
|
**Critical**:
|
||||||
|
- `hole_clearance` must be sufficient to prevent hole from breaking through oval edge. Current value (1mm) tested and verified.
|
||||||
|
- Hole is positioned relative to **white base width**, not blue layer width, to ensure it stays within the white outline.
|
||||||
|
|
||||||
|
## Common Workflows
|
||||||
|
|
||||||
|
### Add New Names to Both Systems
|
||||||
|
1. Edit `Nameplates/names.txt`, add name on new line
|
||||||
|
2. Run both generators:
|
||||||
|
```bash
|
||||||
|
cd Nameplates
|
||||||
|
python generate_nameplates.py # Creates nameplate
|
||||||
|
python generate_zipper_pulls.py # Creates zipper pull
|
||||||
|
```
|
||||||
|
3. Find STLs in `output_stl/` and `zipper-pulls/` respectively
|
||||||
|
|
||||||
|
### Adjust Zipper Pull Hole Clearance
|
||||||
|
If hole is too close to edge or breaking through:
|
||||||
|
1. Edit `zipper_pull_template.scad`, line 17:
|
||||||
|
```scad
|
||||||
|
hole_clearance = 1; // Increase this value
|
||||||
|
```
|
||||||
|
2. Regenerate all zipper pulls:
|
||||||
|
```bash
|
||||||
|
python generate_zipper_pulls.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: Hole clearance is measured from the **white base edge**, not the blue layer edge.
|
||||||
|
|
||||||
|
### Create Custom Dimension Variant
|
||||||
|
To create a new variant (e.g., smaller nameplates):
|
||||||
|
1. Copy existing template: `nameplate_template.scad` → `nameplate_small_template.scad`
|
||||||
|
2. Modify dimensions at top of new file
|
||||||
|
3. Copy generator script: `generate_nameplates.py` → `generate_nameplates_small.py`
|
||||||
|
4. Update generator to use new template and output directory:
|
||||||
|
```python
|
||||||
|
template_file = "nameplate_small_template.scad"
|
||||||
|
output_dir = "output_stl_small"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scale Keycaps for Different Tolerances
|
||||||
|
1. Open `Key caps/Body5_scaled_FIXED.scad` in OpenSCAD GUI
|
||||||
|
2. Adjust `body_scale_xy` parameter at top (default: 0.98 = 98%)
|
||||||
|
- Too tight: decrease to 0.97 (3% reduction)
|
||||||
|
- Too loose: increase to 0.99 (1% reduction)
|
||||||
|
3. Press `F5` for preview, `F6` for full render
|
||||||
|
4. Export via `File > Export > Export as STL`
|
||||||
|
5. Test print and iterate
|
||||||
|
|
||||||
|
See `Key caps/README_scaling.md` for detailed keycap scaling workflow.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Font Not Found (Nameplates/Zipper Pulls)
|
||||||
|
**Symptom**: Text is missing or uses default font in generated STLs.
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
1. Install `Fordscript.ttf`: Right-click → "Install for all users"
|
||||||
|
2. Restart OpenSCAD if running
|
||||||
|
3. Test with manual command:
|
||||||
|
```powershell
|
||||||
|
& "C:\Program Files\OpenSCAD\openscad.exe" -D 'name="Test"' -o test.stl nameplate_template.scad
|
||||||
|
```
|
||||||
|
4. Open `test.stl` in slicer and verify text appears
|
||||||
|
|
||||||
|
### OpenSCAD Path Not Found
|
||||||
|
**Symptom**: `FileNotFoundError: OpenSCAD not found`
|
||||||
|
|
||||||
|
**Fix**: Update hardcoded path in generator scripts:
|
||||||
|
```python
|
||||||
|
# In generate_nameplates.py and generate_zipper_pulls.py, line 36:
|
||||||
|
cmd = [
|
||||||
|
r'C:\Program Files\OpenSCAD\openscad.exe', # ← Update this path
|
||||||
|
# ...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zipper Pull Hole Breaking Through Edge
|
||||||
|
**Symptom**: Hole intersects with oval edge, causing weak point or break.
|
||||||
|
|
||||||
|
**Fix**: Increase `hole_clearance` in `zipper_pull_template.scad`:
|
||||||
|
```scad
|
||||||
|
hole_clearance = 1.5; // Increase from 1mm to 1.5mm
|
||||||
|
```
|
||||||
|
Then regenerate all zipper pulls.
|
||||||
|
|
||||||
|
**Note**: Ensure hole clearance is measured from the white base edge (100mm total width), not the blue layer edge (96mm).
|
||||||
|
|
||||||
|
### Keycap Stem Scaled Incorrectly
|
||||||
|
**Symptom**: Cherry MX stem doesn't fit switch after scaling.
|
||||||
|
|
||||||
|
**Cause**: Using wrong SCAD file (non-FIXED version requires manual stem positioning).
|
||||||
|
|
||||||
|
**Fix**: Always use `Body5_scaled_FIXED.scad` which uses centered import approach. The stem preservation region is automatically centered at origin.
|
||||||
|
|
||||||
|
## Advanced Customization
|
||||||
|
|
||||||
|
### Add Custom Parameter to Templates
|
||||||
|
To add a new parameter (e.g., border thickness):
|
||||||
|
|
||||||
|
1. Add parameter to template:
|
||||||
|
```scad
|
||||||
|
border_thickness = 0.5; // New parameter
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Use in geometry:
|
||||||
|
```scad
|
||||||
|
module base_layer() {
|
||||||
|
difference() {
|
||||||
|
oval(oval_width, oval_height, base_thickness);
|
||||||
|
// Cut border groove
|
||||||
|
translate([0, 0, base_thickness - border_thickness])
|
||||||
|
oval(oval_width - 4, oval_height - 4, border_thickness + 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Pass from Python script (optional):
|
||||||
|
```python
|
||||||
|
cmd = [
|
||||||
|
r'C:\Program Files\OpenSCAD\openscad.exe',
|
||||||
|
'-D', f'name="{escaped_name}"',
|
||||||
|
'-D', f'border_thickness={thickness}', # Override parameter
|
||||||
|
'-o', output_file,
|
||||||
|
template_file
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch Generate Multiple Sizes
|
||||||
|
Modify generator script to loop over multiple configurations:
|
||||||
|
|
||||||
|
```python
|
||||||
|
configs = [
|
||||||
|
{"template": "nameplate_template.scad", "width": 100, "height": 38},
|
||||||
|
{"template": "nameplate_template.scad", "width": 80, "height": 30},
|
||||||
|
{"template": "nameplate_template.scad", "width": 60, "height": 23},
|
||||||
|
]
|
||||||
|
|
||||||
|
for name in names:
|
||||||
|
for config in configs:
|
||||||
|
output_file = f"{config['width']}x{config['height']}_{safe_name}.stl"
|
||||||
|
cmd = [
|
||||||
|
r'C:\Program Files\OpenSCAD\openscad.exe',
|
||||||
|
'-D', f'name="{escaped_name}"',
|
||||||
|
'-D', f'oval_width={config["width"]}',
|
||||||
|
'-D', f'oval_height={config["height"]}',
|
||||||
|
'-o', output_file,
|
||||||
|
config["template"]
|
||||||
|
]
|
||||||
|
subprocess.run(cmd, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Reference
|
||||||
|
|
||||||
|
### OpenSCAD Command Line Flags
|
||||||
|
```powershell
|
||||||
|
# Override parameter
|
||||||
|
-D 'name="value"'
|
||||||
|
|
||||||
|
# Output file
|
||||||
|
-o output.stl
|
||||||
|
|
||||||
|
# Check syntax (no output)
|
||||||
|
--check
|
||||||
|
|
||||||
|
# Verbose output
|
||||||
|
--verbose
|
||||||
|
|
||||||
|
# Clear cache (useful when changing imports)
|
||||||
|
--clear-cache
|
||||||
|
|
||||||
|
# Version info
|
||||||
|
--version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python Subprocess Pattern
|
||||||
|
All generators use the same pattern:
|
||||||
|
```python
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
capture_output=True, # Capture stdout/stderr
|
||||||
|
text=True, # Return strings, not bytes
|
||||||
|
check=True # Raise CalledProcessError on non-zero exit
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Error handling:
|
||||||
|
```python
|
||||||
|
try:
|
||||||
|
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Error: {e.stderr}")
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("OpenSCAD not found. Install from https://openscad.org")
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Organization Guidelines
|
||||||
|
|
||||||
|
### When to Create New Template vs Modify Existing
|
||||||
|
|
||||||
|
**Create New Template** if:
|
||||||
|
- Fundamentally different design (e.g., adding holes, changing shape)
|
||||||
|
- Different use case (nameplates vs zipper pulls)
|
||||||
|
- Need to maintain both versions simultaneously
|
||||||
|
|
||||||
|
**Modify Existing Template** if:
|
||||||
|
- Adjusting dimensions only
|
||||||
|
- Tweaking existing features
|
||||||
|
- Single-use or temporary change
|
||||||
|
|
||||||
|
### Output Directory Naming
|
||||||
|
- Use descriptive names: `output_stl/`, `zipper-pulls/`
|
||||||
|
- Keep separate output directories for different variants
|
||||||
|
- Never mix different variants in the same directory (prevents confusion)
|
||||||
|
|
||||||
|
### Generator Script Naming
|
||||||
|
- Match template name: `generate_nameplates.py` uses `nameplate_template.scad`
|
||||||
|
- Be explicit: `generate_zipper_pulls.py` not `generate_zippers.py`
|
||||||
|
- One template per generator for clarity
|
||||||
|
|
||||||
|
## 3D Printing Notes
|
||||||
|
|
||||||
|
### Recommended Slicer Settings
|
||||||
|
- **Material**: Two colors (white + blue PLA recommended)
|
||||||
|
- **Layer Height**: 0.1-0.2mm for clean text details
|
||||||
|
- **Infill**: 20-30% (thin parts don't need much)
|
||||||
|
- **Supports**: Not needed (flat design, print as modeled)
|
||||||
|
- **Orientation**: Print flat on bed
|
||||||
|
- **Bed Adhesion**: Brim or raft recommended for thin parts
|
||||||
|
- **First Layer**: Critical for thin 1.5mm base - ensure good adhesion
|
||||||
|
|
||||||
|
### Multi-Color Printing
|
||||||
|
Two approaches:
|
||||||
|
1. **Filament swap**: Print white base, pause, swap to blue, continue
|
||||||
|
2. **Two prints**: Print white base separately, print blue top, glue together
|
||||||
|
|
||||||
|
**Important**: Text must be fully engraved through blue layer to show white underneath.
|
||||||
|
|
||||||
|
#### M600 Filament Change Height
|
||||||
|
When using M600 command for automatic filament change in slicer:
|
||||||
|
- **Base layer thickness**: 1.5mm
|
||||||
|
- **Actual M600 height**: 1.5mm + one layer height
|
||||||
|
- **Reason**: M600 triggers BEFORE the specified layer is printed. If set to exactly 1.5mm, the slicer will print one solid white layer at 1.5mm, then trigger M600, then start the blue outline and text.
|
||||||
|
- **Example**: With 0.2mm layer height, set M600 at 1.7mm (1.5 + 0.2) to ensure filament change happens at the correct layer.
|
||||||
|
- **Verification**: After slicing, verify that the layer immediately after M600 begins the blue top layer, not another white layer.
|
||||||
|
|
||||||
|
## Project-Specific Quirks
|
||||||
|
|
||||||
|
### Why Two Separate Templates for Nameplates/Zipper Pulls?
|
||||||
|
- Different dimensions (38mm vs 34mm height)
|
||||||
|
- Zipper pulls need hole, nameplates don't
|
||||||
|
- Maintains backward compatibility with existing nameplate output
|
||||||
|
- Allows independent evolution of each variant
|
||||||
|
|
||||||
|
### Why Hardcoded OpenSCAD Path?
|
||||||
|
- Windows standard installation location is consistent
|
||||||
|
- Avoids PATH environment complexity
|
||||||
|
- Easy to update in one place per script
|
||||||
|
- Future: Consider environment variable or config file
|
||||||
|
|
||||||
|
### Why Manual Font Installation?
|
||||||
|
- OpenSCAD uses system font registry, not file paths
|
||||||
|
- Ensures consistent font rendering across machines
|
||||||
|
- Font must be available to OpenSCAD process
|
||||||
|
- No programmatic workaround exists
|
||||||
|
|
||||||
|
### Keycap Stem Preservation Algorithm
|
||||||
|
- Circular region around stem preserved at 100% scale
|
||||||
|
- Rest of keycap scaled down for better fit
|
||||||
|
- Cherry MX stem is exactly 4mm × 4mm by spec (must not scale)
|
||||||
|
- Circular preservation accommodates off-brand switch tolerances
|
||||||
|
- See `Key caps/CLAUDE.md` for detailed algorithm explanation
|
||||||
1
Printing/Key caps/.claude/commands
Normal file
@@ -0,0 +1 @@
|
|||||||
|
C:/Users/Fred/claude-shared/commands
|
||||||
16
Printing/Key caps/.claude/index.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /Printing/Key caps/.claude/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /Printing/Key caps/.claude/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href="commands">commands</a></li>
|
||||||
|
<li><a href="settings.local.json">settings.local.json</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
Printing/Key caps/.claude/settings.local.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(where:*)",
|
||||||
|
"Bash(if exist \"C:\\Program Files\\OpenSCAD\\openscad.exe\" echo Found at C:Program FilesOpenSCADopenscad.exe)",
|
||||||
|
"Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" -o \"Body5_scaled_98percent.stl\" \"Body5_scaled.scad\")",
|
||||||
|
"Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" --clear-cache -o \"Body5_scaled_98percent.stl\" \"Body5_scaled.scad\")",
|
||||||
|
"Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" -o \"Body5_TEST_circle.stl\" \"Body5_test_circle.scad\")",
|
||||||
|
"Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" -o \"test_cylinder.stl\" \"test_cylinder.scad\")",
|
||||||
|
"Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" -o \"Body5_scaled_FIXED_98percent.stl\" \"Body5_scaled_FIXED.scad\")",
|
||||||
|
"Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" --clear-cache -o \"Body5(1)_scaled_98percent.stl\" \"Body5(1)_scaled.scad\")",
|
||||||
|
"Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" -o \"Body5(1)_scaled_98percent.stl\" \"Body5(1)_scaled.scad\")",
|
||||||
|
"Bash(ls:*)",
|
||||||
|
"Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" -D number_to_generate=1 -o \"Body5_num1.stl\" \"generate_numbered_keycaps.scad\")",
|
||||||
|
"Bash(cmd /c generate_all_numbered_keycaps.bat)",
|
||||||
|
"Bash(for i in {2..20})",
|
||||||
|
"Bash(do \"/c/Program Files/OpenSCAD/openscad.exe\" -D number_to_generate=$i -o \"Body5_num$i.stl\" \"generate_numbered_keycaps.scad\")",
|
||||||
|
"Bash(done)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
55
Printing/Key caps/Body5(1)_scaled.scad
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// =====================================================
|
||||||
|
// Keycap Body Scaling Script - FIXED VERSION
|
||||||
|
// =====================================================
|
||||||
|
// This script scales down the keycap body while preserving
|
||||||
|
// the Cherry MX stem at its original 4mm x 4mm dimensions
|
||||||
|
//
|
||||||
|
// FIX: Centers the model first so cylinder operations work correctly
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
// ----- ADJUSTABLE PARAMETERS -----
|
||||||
|
|
||||||
|
body_scale_xy = 0.98; // 98% of original (2% smaller)
|
||||||
|
body_scale_z = 1.00; // Keep height at 100%
|
||||||
|
|
||||||
|
stem_diameter = 4.8; // Diameter of circular stem preservation region (4.8mm to preserve 4mm x 4mm cross)
|
||||||
|
stem_height = 10.0; // Height of stem region to preserve (tall enough to capture entire stem)
|
||||||
|
stem_z_offset = -1.0; // Z offset to position cylinder on the stem
|
||||||
|
$fn = 128; // Circle smoothness
|
||||||
|
|
||||||
|
stl_filename = "Body5(1).stl";
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// MAIN MODEL - CENTERED APPROACH
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
// Center the imported model at origin
|
||||||
|
// The stem cross should be centered at X=209, Y=223.5 in the original STL
|
||||||
|
module centered_import() {
|
||||||
|
translate([-209, -223.5, 0]) // Center X and Y on the stem
|
||||||
|
import(stl_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
union() {
|
||||||
|
// Part 1: Scaled body WITHOUT the circular stem region
|
||||||
|
difference() {
|
||||||
|
scale([body_scale_xy, body_scale_xy, body_scale_z]) {
|
||||||
|
centered_import();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut out a cylinder where the stem is
|
||||||
|
translate([0, 0, stem_z_offset]) {
|
||||||
|
cylinder(h = stem_height, d = stem_diameter + 0.5, center = true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part 2: Original UNSCALED stem region (circular)
|
||||||
|
intersection() {
|
||||||
|
centered_import(); // NOT scaled - preserves original 4mm x 4mm cross
|
||||||
|
|
||||||
|
// Cylinder defines the 4.8mm circular stem region to keep at 100% scale
|
||||||
|
translate([0, 0, stem_z_offset]) {
|
||||||
|
cylinder(h = stem_height, d = stem_diameter, center = true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
98
Printing/Key caps/Body5_scaled.scad
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
// =====================================================
|
||||||
|
// Keycap Body Scaling Script
|
||||||
|
// =====================================================
|
||||||
|
// This script scales down the keycap body while preserving
|
||||||
|
// the Cherry MX stem at its original 4mm x 4mm dimensions
|
||||||
|
// within a CIRCULAR region (for off-brand switches)
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// 1. Adjust body_scale_xy below (start with 0.98 = 98%)
|
||||||
|
// 2. Adjust stem_x and stem_y to position circle on the cross
|
||||||
|
// 3. Press F5 to preview, F6 to render
|
||||||
|
// 4. Export STL: File > Export > Export as STL
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
// ----- ADJUSTABLE PARAMETERS -----
|
||||||
|
|
||||||
|
// Body scaling factor for X and Y axes
|
||||||
|
body_scale_xy = 0.98; // 98% of original (2% smaller)
|
||||||
|
body_scale_z = 1.00; // Keep height at 100%
|
||||||
|
|
||||||
|
// ----- STEM DIMENSIONS -----
|
||||||
|
// Position the circular preservation region on the stem cross
|
||||||
|
|
||||||
|
stem_x = 209; // X position of stem center (adjust to align with cross)
|
||||||
|
stem_y = 223.5; // Y position of stem center (adjust to align with cross)
|
||||||
|
stem_z = 3.85; // Z position of stem center (adjust if needed)
|
||||||
|
|
||||||
|
stem_diameter = 5.5; // Diameter of circular region around the stem
|
||||||
|
stem_height = 8.0; // Height of stem region to preserve
|
||||||
|
|
||||||
|
$fn = 128; // Circle smoothness (128 = very smooth, 64 = balanced)
|
||||||
|
|
||||||
|
stl_filename = "Body5.stl";
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// MAIN MODEL
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
union() {
|
||||||
|
// Part 1: Scaled body WITHOUT the circular stem region
|
||||||
|
difference() {
|
||||||
|
// The scaled keycap body (scaled around origin, then positioned)
|
||||||
|
translate([stem_x * (1 - body_scale_xy), stem_y * (1 - body_scale_xy), 0]) {
|
||||||
|
scale([body_scale_xy, body_scale_xy, body_scale_z]) {
|
||||||
|
import(stl_filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut out a cylinder where the stem is
|
||||||
|
translate([stem_x, stem_y, stem_z]) {
|
||||||
|
cylinder(
|
||||||
|
h = stem_height,
|
||||||
|
d = stem_diameter + 0.5,
|
||||||
|
center = true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part 2: Original stem at 100% scale in CIRCULAR region
|
||||||
|
intersection() {
|
||||||
|
// The original unscaled model
|
||||||
|
import(stl_filename);
|
||||||
|
|
||||||
|
// A cylinder that defines the CIRCULAR stem region to keep
|
||||||
|
translate([stem_x, stem_y, stem_z]) {
|
||||||
|
cylinder(
|
||||||
|
h = stem_height,
|
||||||
|
d = stem_diameter,
|
||||||
|
center = true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// NOTES
|
||||||
|
// =====================================================
|
||||||
|
//
|
||||||
|
// ADJUSTING THE CIRCULAR REGION POSITION:
|
||||||
|
// If the circle is not centered on the cross stem:
|
||||||
|
// 1. Open in OpenSCAD and press F5 to preview
|
||||||
|
// 2. Rotate the view to see the stem from below
|
||||||
|
// 3. Adjust stem_x and stem_y values until circle is centered on cross
|
||||||
|
// 4. Typical adjustment: try values between 208-210 for X, 222-225 for Y
|
||||||
|
//
|
||||||
|
// ITERATIVE TESTING WORKFLOW:
|
||||||
|
// 1. Start with body_scale_xy = 0.98 (98%)
|
||||||
|
// 2. Export STL and test print
|
||||||
|
// 3. If keycap is still too tight, decrease to 0.97
|
||||||
|
// 4. If keycap is too loose, increase to 0.99
|
||||||
|
// 5. Fine-tune in 0.005 increments (e.g., 0.975, 0.985)
|
||||||
|
//
|
||||||
|
// STEM DIAMETER ADJUSTMENT:
|
||||||
|
// - Increase stem_diameter if you need more clearance around the cross
|
||||||
|
// - Decrease stem_diameter for tighter tolerance
|
||||||
|
// - Typical range: 4.5mm to 6.0mm
|
||||||
|
//
|
||||||
|
// =====================================================
|
||||||
53
Printing/Key caps/Body5_scaled_FIXED.scad
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// =====================================================
|
||||||
|
// Keycap Body Scaling Script - FIXED VERSION
|
||||||
|
// =====================================================
|
||||||
|
// This script scales down the keycap body while preserving
|
||||||
|
// the Cherry MX stem at its original 4mm x 4mm dimensions
|
||||||
|
//
|
||||||
|
// FIX: Centers the model first so cylinder operations work correctly
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
// ----- ADJUSTABLE PARAMETERS -----
|
||||||
|
|
||||||
|
body_scale_xy = 0.98; // 98% of original (2% smaller)
|
||||||
|
body_scale_z = 1.00; // Keep height at 100%
|
||||||
|
|
||||||
|
stem_diameter = 5.5; // Diameter of circular stem preservation region
|
||||||
|
stem_height = 8.0; // Height of stem region to preserve
|
||||||
|
$fn = 128; // Circle smoothness
|
||||||
|
|
||||||
|
stl_filename = "Body5.stl";
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// MAIN MODEL - CENTERED APPROACH
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
// First, we need to center the imported model
|
||||||
|
// The original STL is positioned around x≈209, y≈220
|
||||||
|
// We'll measure the bounding box and center it
|
||||||
|
|
||||||
|
module centered_import() {
|
||||||
|
// Import and center the model at origin
|
||||||
|
translate([-209, -223.5, -4.9]) // Approximate center based on STL coordinates
|
||||||
|
import(stl_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
union() {
|
||||||
|
// Part 1: Scaled body WITHOUT the circular stem region
|
||||||
|
difference() {
|
||||||
|
scale([body_scale_xy, body_scale_xy, body_scale_z]) {
|
||||||
|
centered_import();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut out a cylinder where the stem is (now centered at origin)
|
||||||
|
cylinder(h = stem_height, d = stem_diameter + 0.5, center = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part 2: Original unscaled stem region (circular)
|
||||||
|
intersection() {
|
||||||
|
centered_import();
|
||||||
|
|
||||||
|
// Cylinder defines the circular stem region to keep
|
||||||
|
cylinder(h = stem_height, d = stem_diameter, center = true);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
Printing/Key caps/Body5_test_circle.scad
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// TEST VERSION - Exaggerated circular cutout to verify it's working
|
||||||
|
|
||||||
|
body_scale_xy = 0.98;
|
||||||
|
body_scale_z = 1.00;
|
||||||
|
|
||||||
|
stem_diameter = 8.0; // MUCH larger to make the circle obvious
|
||||||
|
stem_height = 10.0; // Taller to ensure we capture everything
|
||||||
|
stem_z_position = 0;
|
||||||
|
$fn = 128;
|
||||||
|
|
||||||
|
stl_filename = "Body5.stl";
|
||||||
|
|
||||||
|
union() {
|
||||||
|
difference() {
|
||||||
|
scale([body_scale_xy, body_scale_xy, body_scale_z]) {
|
||||||
|
import(stl_filename);
|
||||||
|
}
|
||||||
|
// Large obvious cylinder
|
||||||
|
translate([0, 0, stem_z_position]) {
|
||||||
|
cylinder(h = 20, d = stem_diameter + 0.5, center = true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
intersection() {
|
||||||
|
import(stl_filename);
|
||||||
|
// Large obvious cylinder
|
||||||
|
translate([0, 0, stem_z_position]) {
|
||||||
|
cylinder(h = 20, d = stem_diameter, center = true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
80
Printing/Key caps/CLAUDE.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is an OpenSCAD project for scaling 3D-printed mechanical keyboard keycaps while preserving the Cherry MX stem dimensions. The project contains scripts to reduce keycap body dimensions for better socket fit while maintaining the exact 4mm × 4mm stem cross dimensions required for Cherry MX switches.
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
### Rendering STL Files
|
||||||
|
|
||||||
|
On Windows, OpenSCAD is typically installed at `C:\Program Files\OpenSCAD\openscad.exe`. Use Git Bash path format:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Render a SCAD file to STL
|
||||||
|
"/c/Program Files/OpenSCAD/openscad.exe" -o "output.stl" "input.scad"
|
||||||
|
|
||||||
|
# Clear cache and render (recommended when changing imports)
|
||||||
|
"/c/Program Files/OpenSCAD/openscad.exe" --clear-cache -o "output.stl" "input.scad"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify OpenSCAD Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if OpenSCAD is installed
|
||||||
|
if exist "C:\Program Files\OpenSCAD\openscad.exe" echo Found at C:\Program Files\OpenSCAD\openscad.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Architecture
|
||||||
|
|
||||||
|
### Main Scripts
|
||||||
|
|
||||||
|
- **`Body5_scaled_FIXED.scad`**: The primary working script that uses a centered-import approach. This is the recommended version for scaling keycaps.
|
||||||
|
- **`Body5_scaled.scad`**: Earlier version that manually positions the stem preservation cylinder using explicit X/Y coordinates.
|
||||||
|
- **`Body5_test_circle.scad`**: Test script with exaggerated parameters (larger cylinder) to verify the circular stem preservation is working.
|
||||||
|
- **`test_cylinder.scad`**: Simple cylinder rendering test for OpenSCAD verification.
|
||||||
|
|
||||||
|
### STL Files
|
||||||
|
|
||||||
|
- **`Body5.stl`**: The original keycap model that gets imported and processed by the SCAD scripts.
|
||||||
|
- **`Body2.stl`**: Alternative keycap model.
|
||||||
|
- **Generated STL files**: Output from rendering the SCAD scripts (e.g., `Body5_scaled_98percent.stl`).
|
||||||
|
|
||||||
|
### Scaling Algorithm
|
||||||
|
|
||||||
|
The scripts use a two-part union approach:
|
||||||
|
|
||||||
|
1. **Scaled Body with Cutout**: The entire keycap is scaled down by `body_scale_xy`, then a cylinder is subtracted where the stem should be
|
||||||
|
2. **Unscaled Stem Region**: A cylinder intersection preserves the original stem region at 100% scale
|
||||||
|
3. **Union**: Both parts are merged, creating a keycap with scaled body but original stem dimensions
|
||||||
|
|
||||||
|
The `_FIXED` version centers the model at the origin before operations, making cylinder placement simpler. The non-FIXED version requires manual positioning of the preservation cylinder using `stem_x` and `stem_y` coordinates.
|
||||||
|
|
||||||
|
### Critical Parameters
|
||||||
|
|
||||||
|
- **`body_scale_xy`**: X/Y scaling factor (typically 0.97-0.99 for 1-3% reduction)
|
||||||
|
- **`body_scale_z`**: Z scaling factor (keep at 1.00 to maintain keycap height)
|
||||||
|
- **`stem_diameter`**: Diameter of circular preservation region (default: 5.5mm)
|
||||||
|
- **`stem_height`**: Height of stem region (default: 8.0mm)
|
||||||
|
- **`$fn`**: OpenSCAD circle resolution (64-128, higher = smoother but slower)
|
||||||
|
|
||||||
|
### Centering Approach (FIXED version)
|
||||||
|
|
||||||
|
The `centered_import()` module translates the imported STL by `[-209, -223.5, -4.9]` to center it at the origin. These values were determined by analyzing the original STL's bounding box. This allows stem operations to use simple `cylinder()` calls at the origin rather than calculating offsets.
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
1. Modify SCAD script parameters (typically just `body_scale_xy`)
|
||||||
|
2. Render to STL using OpenSCAD command line or GUI (F5 preview, F6 full render in GUI)
|
||||||
|
3. Import STL into slicer and print test keycap
|
||||||
|
4. Measure fit and iterate on scaling factor
|
||||||
|
5. Typical iteration: start at 0.98, adjust by 0.01 increments based on fit testing
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- The Cherry MX stem cross is exactly 4mm × 4mm by specification and must not be scaled
|
||||||
|
- The circular preservation region accommodates off-brand switches that may have slightly different tolerances
|
||||||
|
- All SCAD scripts expect `Body5.stl` (or specified STL file) to be in the same directory
|
||||||
|
- Rendering is computationally intensive due to importing the STL twice (once for scaling, once for stem preservation)
|
||||||
109
Printing/Key caps/README_scaling.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# Keycap Scaling Guide
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. **Open the script**: Open `Body5_scaled.scad` in OpenSCAD
|
||||||
|
2. **Adjust scaling**: Edit the `body_scale_xy` parameter at the top (default: 0.98 = 98%)
|
||||||
|
3. **Preview**: Press `F5` to see a quick preview
|
||||||
|
4. **Render**: Press `F6` to fully render (required before export)
|
||||||
|
5. **Export**: Go to `File > Export > Export as STL`
|
||||||
|
6. **Test print**: Print and check the fit
|
||||||
|
7. **Iterate**: Adjust the scale parameter and repeat if needed
|
||||||
|
|
||||||
|
## Key Parameters
|
||||||
|
|
||||||
|
### `body_scale_xy`
|
||||||
|
- **Default**: `0.98` (98% of original size)
|
||||||
|
- **Purpose**: Scales the X and Y dimensions of the keycap body
|
||||||
|
- **Recommendations**:
|
||||||
|
- Start with `0.98` for a 2% reduction
|
||||||
|
- If too tight: try `0.97` (3% reduction)
|
||||||
|
- If too loose: try `0.99` (1% reduction)
|
||||||
|
- Fine-tune: use `0.975`, `0.985`, etc.
|
||||||
|
|
||||||
|
### `body_scale_z`
|
||||||
|
- **Default**: `1.00` (no scaling)
|
||||||
|
- **Purpose**: Scales the Z (height) dimension
|
||||||
|
- **Recommendation**: Keep at `1.00` to maintain keycap height
|
||||||
|
|
||||||
|
### Stem Parameters (Advanced)
|
||||||
|
Only adjust these if the stem preservation isn't working correctly:
|
||||||
|
|
||||||
|
- `stem_diameter`: Diameter of the circular preservation region (default: 5.5mm)
|
||||||
|
- `stem_height`: Height of the stem (default: 4.0mm)
|
||||||
|
- `stem_z_position`: Vertical position offset (default: -2.0mm)
|
||||||
|
- `$fn`: Circle smoothness (default: 64, range: 32-128)
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
The script uses a two-part approach:
|
||||||
|
|
||||||
|
1. **Scaled Body**: The entire keycap is scaled down by your chosen percentage, BUT a circular region around the stem is cut out
|
||||||
|
2. **Original Stem**: The circular stem region is preserved at 100% scale (keeping the 4mm × 4mm Cherry MX cross dimensions)
|
||||||
|
3. **Combined**: Both parts are merged together
|
||||||
|
|
||||||
|
This ensures:
|
||||||
|
- ✅ The outer keycap body fits better in sockets (scaled down)
|
||||||
|
- ✅ The Cherry MX stem remains exactly 4mm × 4mm (unscaled)
|
||||||
|
- ✅ The circular region around the stem is preserved (for off-brand switches)
|
||||||
|
- ✅ The stem stays centered and properly positioned
|
||||||
|
|
||||||
|
## Workflow for Iterative Testing
|
||||||
|
|
||||||
|
1. Export STL with `body_scale_xy = 0.98`
|
||||||
|
2. Slice and print the keycap
|
||||||
|
3. Test fit on your keyboard
|
||||||
|
4. **If too tight**: Decrease scale (try 0.97)
|
||||||
|
5. **If too loose**: Increase scale (try 0.99)
|
||||||
|
6. **If just right**: You're done!
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### The stem looks wrong or is scaled
|
||||||
|
- The circular stem region might not be positioned correctly
|
||||||
|
- Measure your stem in the original STL
|
||||||
|
- Adjust `stem_z_position`, `stem_diameter`, or `stem_height`
|
||||||
|
- If the circle is too jagged, increase `$fn` to 128
|
||||||
|
|
||||||
|
### The keycap won't render
|
||||||
|
- Make sure `Body5.stl` is in the same folder as the `.scad` file
|
||||||
|
- Check for errors in the OpenSCAD console (bottom of window)
|
||||||
|
|
||||||
|
### Rendering is slow
|
||||||
|
- This is normal! The script imports the STL twice
|
||||||
|
- `F5` preview is fast but lower quality
|
||||||
|
- `F6` render is slow but required for STL export
|
||||||
|
- Consider reducing the preview quality in OpenSCAD preferences
|
||||||
|
|
||||||
|
### The seam between body and stem is visible
|
||||||
|
- This is usually not noticeable in the printed part
|
||||||
|
- If needed, increase `stem_diameter` by 0.5mm increments (try 6.0mm or 6.5mm)
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
Key caps/
|
||||||
|
├── Body5.stl # Original keycap STL
|
||||||
|
├── Body5_scaled.scad # OpenSCAD scaling script
|
||||||
|
└── README_scaling.md # This guide
|
||||||
|
```
|
||||||
|
|
||||||
|
## Export Settings
|
||||||
|
|
||||||
|
When exporting STL from OpenSCAD:
|
||||||
|
- File format: STL (binary is smaller, ASCII is more compatible)
|
||||||
|
- Units: millimeters (mm)
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
After exporting your scaled STL:
|
||||||
|
1. Import into your slicer (Cura, PrusaSlicer, etc.)
|
||||||
|
2. Use typical keycap print settings:
|
||||||
|
- Layer height: 0.1-0.2mm
|
||||||
|
- Infill: 15-20%
|
||||||
|
- Supports: Probably not needed for most keycaps
|
||||||
|
3. Print and test!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Note**: The Cherry MX stem dimensions (4mm × 4mm) are standardized. The script is designed to preserve these exactly, so you should only need to adjust the `body_scale_xy` parameter for tolerance testing.
|
||||||
34
Printing/Key caps/generate_all_numbered_keycaps.bat
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
@echo off
|
||||||
|
REM =====================================================
|
||||||
|
REM Batch Script to Generate All 20 Numbered Keycaps
|
||||||
|
REM =====================================================
|
||||||
|
REM This script uses OpenSCAD command line to generate
|
||||||
|
REM all 20 numbered keycap STL files automatically
|
||||||
|
REM =====================================================
|
||||||
|
|
||||||
|
echo Starting generation of 20 numbered keycap STL files...
|
||||||
|
echo.
|
||||||
|
|
||||||
|
set OPENSCAD="C:\Program Files\OpenSCAD\openscad.exe"
|
||||||
|
set SCRIPT=generate_numbered_keycaps.scad
|
||||||
|
|
||||||
|
REM Loop through numbers 1-20
|
||||||
|
for /L %%i in (1,1,20) do (
|
||||||
|
echo Generating Body5_num%%i.stl...
|
||||||
|
%OPENSCAD% -D number_to_generate=%%i -o Body5_num%%i.stl %SCRIPT%
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo ERROR: Failed to generate Body5_num%%i.stl
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
echo Completed Body5_num%%i.stl
|
||||||
|
echo.
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo =====================================================
|
||||||
|
echo All 20 numbered keycap STL files generated successfully!
|
||||||
|
echo Files created: Body5_num1.stl through Body5_num20.stl
|
||||||
|
echo =====================================================
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
102
Printing/Key caps/generate_numbered_keycaps.scad
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
// =====================================================
|
||||||
|
// Numbered Keycap Generator - Multi-Color Printing
|
||||||
|
// =====================================================
|
||||||
|
// This script generates 20 numbered keycap STL files
|
||||||
|
// with recessed numbers for multi-material printing
|
||||||
|
//
|
||||||
|
// Numbers are 14mm tall, recessed 0.6mm deep
|
||||||
|
// Positioned on triangular top face, centered on apex-to-midpoint line
|
||||||
|
// Apex oriented as "up" (aligned with Cherry MX cross arm)
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
// ----- ADJUSTABLE PARAMETERS -----
|
||||||
|
|
||||||
|
number_to_generate = 1; // Change this value (1-20) to generate different numbers
|
||||||
|
|
||||||
|
number_height = 14; // Height of numbers in mm
|
||||||
|
number_depth = 0.6; // Recess depth for color saturation
|
||||||
|
text_font = "Liberation Sans:style=Bold"; // Font (use bold for visibility)
|
||||||
|
text_thickness = number_depth; // Same as recess depth
|
||||||
|
|
||||||
|
// Triangular face positioning
|
||||||
|
// The top face is opposite the stem pocket
|
||||||
|
// One apex aligns with a Cherry MX cross arm
|
||||||
|
face_z_position = 8.0; // Approximate Z height of top triangular face
|
||||||
|
face_rotation = 0; // Rotation to align apex with cross arm
|
||||||
|
text_y_offset = 0; // Offset along apex-to-midpoint centerline
|
||||||
|
|
||||||
|
$fn = 128; // Circle smoothness
|
||||||
|
|
||||||
|
base_stl_filename = "Body5(1)_scaled_98percent.stl";
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// CENTERED IMPORT MODULE
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
module centered_base() {
|
||||||
|
// Import the scaled keycap base
|
||||||
|
// Already centered from the previous scaling script
|
||||||
|
import(base_stl_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// NUMBERED TEXT MODULE
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
module recessed_number(num) {
|
||||||
|
// Create text for the number
|
||||||
|
rotate([0, 0, face_rotation]) {
|
||||||
|
translate([0, text_y_offset, face_z_position]) {
|
||||||
|
rotate([0, 0, 0]) { // Text faces up
|
||||||
|
linear_extrude(height = text_thickness + 1) { // +1 to ensure clean cut
|
||||||
|
text(
|
||||||
|
str(num),
|
||||||
|
size = number_height,
|
||||||
|
halign = "center",
|
||||||
|
valign = "center",
|
||||||
|
font = text_font
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// MAIN MODEL
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
difference() {
|
||||||
|
// The base keycap
|
||||||
|
centered_base();
|
||||||
|
|
||||||
|
// Subtract the recessed number
|
||||||
|
recessed_number(number_to_generate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// USAGE INSTRUCTIONS
|
||||||
|
// =====================================================
|
||||||
|
//
|
||||||
|
// MANUAL GENERATION (one at a time):
|
||||||
|
// 1. Change number_to_generate value (1-20)
|
||||||
|
// 2. Render with F6
|
||||||
|
// 3. Export as STL with desired filename
|
||||||
|
//
|
||||||
|
// COMMAND LINE GENERATION (all 20 files):
|
||||||
|
// Use the companion batch script or run:
|
||||||
|
// openscad -D number_to_generate=N -o Body5_numN.stl generate_numbered_keycaps.scad
|
||||||
|
//
|
||||||
|
// ADJUSTING POSITION:
|
||||||
|
// - face_z_position: Move numbers up/down on the keycap
|
||||||
|
// - face_rotation: Rotate around Z axis to align with cross arm
|
||||||
|
// - text_y_offset: Move along the apex-to-midpoint centerline
|
||||||
|
//
|
||||||
|
// MULTI-MATERIAL PRINTING:
|
||||||
|
// 1. Slice the numbered STL normally
|
||||||
|
// 2. At layer with recess (approx 0.6mm from top), insert material change
|
||||||
|
// 3. Printer will pause, swap filament for number color
|
||||||
|
// 4. Resume printing - number will be filled with new color
|
||||||
|
// 5. At end of recess depth, change back to original material
|
||||||
|
//
|
||||||
|
// =====================================================
|
||||||
24
Printing/Key caps/index.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /Printing/Key caps/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /Printing/Key caps/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href=".claude/">.claude/</a></li>
|
||||||
|
<li><a href="Body5%281%29_scaled.scad">Body5(1)_scaled.scad</a></li>
|
||||||
|
<li><a href="Body5_scaled.scad">Body5_scaled.scad</a></li>
|
||||||
|
<li><a href="Body5_scaled_FIXED.scad">Body5_scaled_FIXED.scad</a></li>
|
||||||
|
<li><a href="Body5_test_circle.scad">Body5_test_circle.scad</a></li>
|
||||||
|
<li><a href="CLAUDE.md">CLAUDE.md</a></li>
|
||||||
|
<li><a href="generate_all_numbered_keycaps.bat">generate_all_numbered_keycaps.bat</a></li>
|
||||||
|
<li><a href="generate_numbered_keycaps.scad">generate_numbered_keycaps.scad</a></li>
|
||||||
|
<li><a href="README_scaling.md">README_scaling.md</a></li>
|
||||||
|
<li><a href="test_cylinder.scad">test_cylinder.scad</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3
Printing/Key caps/test_cylinder.scad
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// Simple cylinder test
|
||||||
|
$fn = 128;
|
||||||
|
cylinder(h = 10, d = 5.0, center = true);
|
||||||
1
Printing/Nameplates/.claude/commands
Normal file
@@ -0,0 +1 @@
|
|||||||
|
C:/Users/Fred/claude-shared/commands
|
||||||
16
Printing/Nameplates/.claude/index.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /Printing/Nameplates/.claude/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /Printing/Nameplates/.claude/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href="commands">commands</a></li>
|
||||||
|
<li><a href="settings.local.json">settings.local.json</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
Printing/Nameplates/.claude/settings.local.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(python:*)",
|
||||||
|
"Bash(dir:*)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Test\"\"\" -o test_output.stl nameplate_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -o checkmark_test.stl checkmark_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Test\"\"\" -o test_hole_position.stl zipper_pull_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Christopher\"\"\" -o test_christopher.stl zipper_pull_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Christopher\"\"\" -o test_christopher_13pt.stl zipper_pull_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Fred\"\"\" -o test_fred_bold.stl zipper_pull_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Zoe\"\"\" -o test_zoe.stl zipper_pull_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Fred\"\"\" -o test_fred_dynamic.stl zipper_pull_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Christopher\"\"\" -o test_christopher_dynamic.stl zipper_pull_template.scad)",
|
||||||
|
"Bash(timeout:*)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Zoe\"\"\" -o test_zoe_raised.stl zipper_pull_raised_text_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Fred\"\"\" -o test_fred_raised.stl zipper_pull_raised_text_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Christopher\"\"\" -o test_christopher_raised.stl zipper_pull_raised_text_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Fred\"\"\" -D \"base_text_size=13\" -o test_fred_debug.stl zipper_pull_raised_text_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -o test_simple_fred.stl test_simple_fred.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Fred\"\"\" -o test_fred_reduced_bold.stl zipper_pull_raised_text_template_REDUCED_BOLD.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Bennet\"\"\" -o test_bennet_facedown.stl zipper_pull_raised_text_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Bennett\"\"\" -o test_bennett_facedown.stl zipper_pull_raised_text_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Bennett\"\"\" -o test_bennett_facedown.3mf zipper_pull_raised_text_template.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -o test_font.stl test_font.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -o test_debug.3mf test_debug.scad)",
|
||||||
|
"Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -o test_blue_only.3mf test_blue_only.scad)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
},
|
||||||
|
"enableAllProjectMcpServers": true,
|
||||||
|
"enabledMcpjsonServers": [
|
||||||
|
"n8n-mcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
278
Printing/Nameplates/.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
# AI Coding Instructions for Nameplates Generator
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
A batch 3D nameplate generator using **Python + OpenSCAD**. Reads names from text file, generates parametric two-layer oval nameplates with engraved text, outputs STL files for 3D printing.
|
||||||
|
|
||||||
|
**Design**: White base (1.5mm) + blue top layer (1mm) with text engraved through blue layer to expose white underneath.
|
||||||
|
|
||||||
|
## Critical Dependencies
|
||||||
|
|
||||||
|
### OpenSCAD Installation
|
||||||
|
- **Path**: `C:\Program Files\OpenSCAD\openscad.exe` (hardcoded in line 36 of `generate_nameplates.py`)
|
||||||
|
- Download: https://openscad.org/downloads.html
|
||||||
|
- **Must update path** if installed elsewhere
|
||||||
|
|
||||||
|
### Font Requirements (CRITICAL)
|
||||||
|
- **Font**: Fordscript cursive font (`Fordscript.ttf` in project root)
|
||||||
|
- **Installation**: Must be installed in Windows (right-click → Install for all users)
|
||||||
|
- **Why**: OpenSCAD references fonts by name, not file path
|
||||||
|
- **Reference in SCAD**: `font="Fordscript"` (line 44 of `nameplate_template.scad`)
|
||||||
|
- **Path hardcoded**: `font_file = "C:/Users/Fred/claude/Fordscript.ttf"` in SCAD (line 10) - informational only, not used by OpenSCAD
|
||||||
|
|
||||||
|
## Architecture & Data Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
names.txt → generate_nameplates.py → OpenSCAD CLI → output_stl/*.stl
|
||||||
|
↑
|
||||||
|
nameplate_template.scad
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pipeline
|
||||||
|
1. **Input**: `names.txt` (one name per line, UTF-8 encoded)
|
||||||
|
2. **Processing**: Python script iterates, calling OpenSCAD for each name
|
||||||
|
3. **OpenSCAD**: Renders 3D model from parametric template
|
||||||
|
4. **Output**: Individual STL files in `output_stl/` directory
|
||||||
|
|
||||||
|
### Key Files
|
||||||
|
- `generate_nameplates.py` - Batch processor, subprocess orchestration
|
||||||
|
- `nameplate_template.scad` - Parametric OpenSCAD template
|
||||||
|
- `names.txt` - Input data (plain text, one name per line)
|
||||||
|
- `output_stl/` - Generated STL files (created automatically)
|
||||||
|
- `Fordscript.ttf` - Custom font (must be installed)
|
||||||
|
|
||||||
|
## Critical Developer Workflows
|
||||||
|
|
||||||
|
### Generate All Nameplates
|
||||||
|
```bash
|
||||||
|
python generate_nameplates.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Single Nameplate (Manual)
|
||||||
|
```powershell
|
||||||
|
# PowerShell syntax
|
||||||
|
& "C:\Program Files\OpenSCAD\openscad.exe" -D 'name="TestName"' -o test_output.stl nameplate_template.scad
|
||||||
|
|
||||||
|
# Or from CMD
|
||||||
|
"C:\Program Files\OpenSCAD\openscad.exe" -D "name=\"TestName\"" -o test_output.stl nameplate_template.scad
|
||||||
|
```
|
||||||
|
|
||||||
|
### Preview in OpenSCAD GUI
|
||||||
|
```powershell
|
||||||
|
# Open template in GUI for visual editing
|
||||||
|
& "C:\Program Files\OpenSCAD\openscad.exe" nameplate_template.scad
|
||||||
|
```
|
||||||
|
|
||||||
|
Then manually edit `name` parameter at top of file to preview different text.
|
||||||
|
|
||||||
|
### Validate Font Installation
|
||||||
|
```powershell
|
||||||
|
# Check if font is installed
|
||||||
|
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" | Select-String "Fordscript"
|
||||||
|
|
||||||
|
# Or check fonts directory
|
||||||
|
ls $env:WINDIR\Fonts\Fordscript*
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project-Specific Conventions
|
||||||
|
|
||||||
|
### Name Sanitization
|
||||||
|
Python script converts names to safe filenames:
|
||||||
|
- Removes special characters (keeps alphanumeric, spaces, hyphens, underscores)
|
||||||
|
- Replaces spaces with underscores
|
||||||
|
- Example: `"John Doe"` → `John_Doe.stl`
|
||||||
|
|
||||||
|
### OpenSCAD Command Pattern
|
||||||
|
```python
|
||||||
|
cmd = [
|
||||||
|
r'C:\Program Files\OpenSCAD\openscad.exe',
|
||||||
|
'-D', f'name="{escaped_name}"', # Set variable
|
||||||
|
'-o', output_file, # Output file
|
||||||
|
template_file # Input SCAD file
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key flags**:
|
||||||
|
- `-D name="value"` - Override parameter in SCAD file
|
||||||
|
- `-o file.stl` - Output file path
|
||||||
|
- No flag for input file (positional argument)
|
||||||
|
|
||||||
|
### SCAD Template Structure
|
||||||
|
```scad
|
||||||
|
// Parameters (overridable via -D flag)
|
||||||
|
name = "NAME";
|
||||||
|
oval_width = 100;
|
||||||
|
oval_height = 38;
|
||||||
|
// ... more parameters
|
||||||
|
|
||||||
|
// Modules (functions)
|
||||||
|
module oval(width, height, depth) { ... }
|
||||||
|
module base_layer() { ... }
|
||||||
|
module top_layer() { ... }
|
||||||
|
module nameplate() { ... }
|
||||||
|
|
||||||
|
// Execution (builds the model)
|
||||||
|
nameplate();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### Font Not Found Error
|
||||||
|
**Symptom**: OpenSCAD renders but text is missing or uses default font.
|
||||||
|
|
||||||
|
**Causes**:
|
||||||
|
1. Font not installed in Windows (right-click TTF → Install)
|
||||||
|
2. Font name mismatch (must be exactly "Fordscript" in SCAD)
|
||||||
|
3. OpenSCAD needs restart after font installation
|
||||||
|
|
||||||
|
**Fix**: Install font, restart OpenSCAD, verify with manual test.
|
||||||
|
|
||||||
|
### Special Characters in Names
|
||||||
|
**Symptom**: Subprocess error or malformed STL.
|
||||||
|
|
||||||
|
**Cause**: Shell escaping issues with quotes, backslashes, etc.
|
||||||
|
|
||||||
|
**Current handling**: `escaped_name = name.replace('"', '\\"')` (line 33)
|
||||||
|
|
||||||
|
**Extension**: Add more sanitization if needed:
|
||||||
|
```python
|
||||||
|
# Escape more shell-sensitive characters
|
||||||
|
escaped_name = name.replace('"', '\\"').replace('\\', '\\\\')
|
||||||
|
```
|
||||||
|
|
||||||
|
### OpenSCAD Path Issues
|
||||||
|
**Symptom**: `FileNotFoundError: OpenSCAD not found`.
|
||||||
|
|
||||||
|
**Fix**: Update hardcoded path in `generate_nameplates.py:36`:
|
||||||
|
```python
|
||||||
|
cmd = [
|
||||||
|
r'C:\Program Files\OpenSCAD\openscad.exe', # ← Update this
|
||||||
|
# ...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Better approach**: Use environment variable or config file:
|
||||||
|
```python
|
||||||
|
OPENSCAD_PATH = os.getenv('OPENSCAD_PATH', r'C:\Program Files\OpenSCAD\openscad.exe')
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modifying Dimensions
|
||||||
|
|
||||||
|
All dimensions in `nameplate_template.scad` (lines 6-12):
|
||||||
|
|
||||||
|
| Parameter | Current Value | Purpose |
|
||||||
|
|-----------|---------------|---------|
|
||||||
|
| `oval_width` | 100mm | Overall width |
|
||||||
|
| `oval_height` | 38mm | Overall height (proportional) |
|
||||||
|
| `base_thickness` | 1.5mm | White layer thickness |
|
||||||
|
| `top_thickness` | 1mm | Blue layer thickness |
|
||||||
|
| `base_offset` | 2mm | White extension beyond blue |
|
||||||
|
| `text_size` | 15mm | Font size |
|
||||||
|
| `text_depth` | 1mm | Engraving depth (full blue layer) |
|
||||||
|
|
||||||
|
### Maintaining Proportions
|
||||||
|
Height typically 38% of width for aesthetic oval shape:
|
||||||
|
```scad
|
||||||
|
oval_height = oval_width * 0.38;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Text Depth Critical
|
||||||
|
`text_depth` must equal `top_thickness` to engrave through entire blue layer:
|
||||||
|
```scad
|
||||||
|
text_depth = 1; // Must match top_thickness for full engraving
|
||||||
|
```
|
||||||
|
|
||||||
|
If `text_depth < top_thickness`, text won't reach white base. If `text_depth > top_thickness`, text cuts into white base (undesirable).
|
||||||
|
|
||||||
|
## Extending the Template
|
||||||
|
|
||||||
|
### Add New Shape Option
|
||||||
|
```scad
|
||||||
|
// Add rectangle module
|
||||||
|
module rectangle(width, height, depth) {
|
||||||
|
cube([width, height, depth], center=true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add shape parameter at top
|
||||||
|
shape = "oval"; // or "rectangle"
|
||||||
|
|
||||||
|
// Use conditional in base_layer
|
||||||
|
module base_layer() {
|
||||||
|
if (shape == "rectangle")
|
||||||
|
rectangle(oval_width + base_offset*2, oval_height + base_offset*2, base_thickness);
|
||||||
|
else
|
||||||
|
oval(oval_width + base_offset*2, oval_height + base_offset*2, base_thickness);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add Logo/Icon
|
||||||
|
```scad
|
||||||
|
// Import SVG or PNG
|
||||||
|
module logo() {
|
||||||
|
translate([0, -oval_height/3, base_thickness + top_thickness - text_depth])
|
||||||
|
linear_extrude(height=text_depth)
|
||||||
|
import("logo.svg", center=true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to top_layer difference
|
||||||
|
difference() {
|
||||||
|
// ... existing blue oval
|
||||||
|
logo(); // Cut logo
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch Different Sizes
|
||||||
|
Modify Python script to pass multiple parameters:
|
||||||
|
```python
|
||||||
|
cmd = [
|
||||||
|
r'C:\Program Files\OpenSCAD\openscad.exe',
|
||||||
|
'-D', f'name="{escaped_name}"',
|
||||||
|
'-D', f'oval_width={width}', # Variable width
|
||||||
|
'-D', f'text_size={text_size}', # Variable text size
|
||||||
|
'-o', output_file,
|
||||||
|
template_file
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting Commands
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Check OpenSCAD version
|
||||||
|
& "C:\Program Files\OpenSCAD\openscad.exe" --version
|
||||||
|
|
||||||
|
# Test SCAD syntax (no output, just validation)
|
||||||
|
& "C:\Program Files\OpenSCAD\openscad.exe" --check nameplate_template.scad
|
||||||
|
|
||||||
|
# Render with verbose output
|
||||||
|
& "C:\Program Files\OpenSCAD\openscad.exe" -D 'name="Test"' -o test.stl nameplate_template.scad --verbose
|
||||||
|
|
||||||
|
# List available fonts in OpenSCAD (run in OpenSCAD console)
|
||||||
|
# Help → Font List
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3D Printing Notes
|
||||||
|
|
||||||
|
- **Material**: Two colors required (white + blue recommended)
|
||||||
|
- **Layer Height**: 0.1-0.2mm for clean text
|
||||||
|
- **Infill**: 20-30% sufficient for thin nameplates
|
||||||
|
- **Supports**: Not needed (flat design)
|
||||||
|
- **Orientation**: Print flat (as modeled)
|
||||||
|
- **Bed Adhesion**: Brim or raft recommended for thin parts
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Add New Name
|
||||||
|
1. Edit `names.txt`, add line with new name
|
||||||
|
2. Run `python generate_nameplates.py`
|
||||||
|
3. Find STL in `output_stl/` directory
|
||||||
|
|
||||||
|
### Change All Dimensions
|
||||||
|
1. Edit parameters at top of `nameplate_template.scad`
|
||||||
|
2. Run `python generate_nameplates.py` to regenerate all
|
||||||
|
3. Or manually test with single name first
|
||||||
|
|
||||||
|
### Install Font on New Machine
|
||||||
|
1. Copy `Fordscript.ttf` to new machine
|
||||||
|
2. Right-click → "Install for all users"
|
||||||
|
3. Restart OpenSCAD if running
|
||||||
|
4. Test with manual OpenSCAD command
|
||||||
15
Printing/Nameplates/.github/index.html
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /Printing/Nameplates/.github/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /Printing/Nameplates/.github/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href="copilot-instructions.md">copilot-instructions.md</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
Printing/Nameplates/300.103.pdf
Normal file
68
Printing/Nameplates/CLAUDE.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is a 3D nameplate generator that creates two-layer oval nameplates with engraved text using OpenSCAD. The system batch-processes names from a text file and outputs STL files ready for 3D printing.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**Pipeline:**
|
||||||
|
1. Python script (`generate_nameplates.py`) reads names from `names.txt`
|
||||||
|
2. For each name, it invokes OpenSCAD CLI with the parametric template
|
||||||
|
3. OpenSCAD renders the 3D model using `nameplate_template.scad`
|
||||||
|
4. STL files are output to `output_stl/` directory
|
||||||
|
|
||||||
|
**Design Structure:**
|
||||||
|
- Two-layer design: white base (1.5mm) + blue top layer (1mm)
|
||||||
|
- Text is engraved completely through the blue layer to expose white underneath
|
||||||
|
- Oval shape created by scaling a cylinder
|
||||||
|
- All dimensions parametric for easy customization
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
**Generate nameplates:**
|
||||||
|
```bash
|
||||||
|
python generate_nameplates.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test single nameplate (manual):**
|
||||||
|
```bash
|
||||||
|
"C:\Program Files\OpenSCAD\openscad.exe" -D "name=\"TestName\"" -o output.stl nameplate_template.scad
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical Configuration
|
||||||
|
|
||||||
|
**OpenSCAD Path:**
|
||||||
|
The script uses hardcoded path: `C:\Program Files\OpenSCAD\openscad.exe`
|
||||||
|
Update in `generate_nameplates.py:36` if OpenSCAD is installed elsewhere.
|
||||||
|
|
||||||
|
**Font Requirements:**
|
||||||
|
- Uses Fordscript font (Fordscript.ttf in project root)
|
||||||
|
- Font MUST be installed in Windows (right-click → Install) for OpenSCAD to access it
|
||||||
|
- OpenSCAD references fonts by name, not file path
|
||||||
|
- Referenced in template as `font="Fordscript"` (line 44)
|
||||||
|
|
||||||
|
**Dimensions:**
|
||||||
|
Default nameplate size: 3" tall × 8" wide (76mm × 200mm)
|
||||||
|
All dimensions in `nameplate_template.scad:6-12`
|
||||||
|
|
||||||
|
## File Purposes
|
||||||
|
|
||||||
|
- `nameplate_template.scad` - OpenSCAD parametric template defining the 3D geometry
|
||||||
|
- `generate_nameplates.py` - Batch processor that calls OpenSCAD for each name
|
||||||
|
- `names.txt` - Input file with one name per line
|
||||||
|
- `Fordscript.ttf` - Custom cursive font (must be installed in Windows)
|
||||||
|
- `output_stl/` - Generated STL files (one per name)
|
||||||
|
|
||||||
|
## Modifying Dimensions
|
||||||
|
|
||||||
|
Key parameters in `nameplate_template.scad`:
|
||||||
|
- `oval_height` - Overall height (currently 76mm = 3 inches)
|
||||||
|
- `oval_width` - Overall width (currently 200mm ≈ 8 inches)
|
||||||
|
- `base_thickness` - White layer thickness (1.5mm)
|
||||||
|
- `top_thickness` - Blue layer thickness (1mm)
|
||||||
|
- `text_depth` - Engraving depth (1mm - cuts through entire blue layer)
|
||||||
|
- `text_size` - Font size (30mm)
|
||||||
|
- `base_offset` - How much white extends beyond blue (2mm)
|
||||||
19092
Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.dxf
Normal file
BIN
Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.eps
Normal file
BIN
Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.png
Normal file
|
After Width: | Height: | Size: 136 KiB |
56
Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.svg
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 720 570.9" style="enable-background:new 0 0 720 570.9;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#7E4E8C;}
|
||||||
|
.st1{fill:#9C6BAD;}
|
||||||
|
</style>
|
||||||
|
<g id="XMLID_3_">
|
||||||
|
<path id="XMLID_188_" d="M717.3,120.1l-1.3-2.8l-0.3-0.8l-0.3-0.5l-0.5-1l-0.5-1l-0.3-0.5v-0.3c-0.5-1-0.3-0.5-0.5-0.8l-1.6-2.3
|
||||||
|
c-1-1.6-2.3-2.8-3.4-4.4c-1.3-1.3-2.6-2.6-3.9-3.9c-11.1-9.8-27.4-13.2-41.4-8.8c-1.8,0.5-3.9,1.3-5.7,2.1l-1.8,1l-1,0.5l-0.8,0.5
|
||||||
|
l-0.5,0.3l-7.2,4.4l-14.8,8.5l-29.2,17.1L582.3,139c0-11.9,0-24.1,0.3-36.2c0-7.2,0-14.2,0-21.5c0-3.6,0-7.2,0-10.9
|
||||||
|
c0-2.1-0.3-4.1-0.3-6.2s-0.5-4.1-0.8-6.2c-2.8-16.8-12.2-32.6-26.1-43.2c-7-5.4-14.8-9.3-23.3-11.9c-2.1-0.5-4.1-1.3-6.5-1.6
|
||||||
|
c-2.3-0.3-4.9-0.8-7-1L512.9,0h-6.2c-15.3,0-30.5,0.3-46.1,0.3c-62.1,0.3-126.3,1.3-191.5,1.8c-65.2,0.8-131.5,1-197.7,1
|
||||||
|
c-18.6-0.3-37,8-49.4,21.5c-6.2,6.7-11.1,14.8-14.2,23.5c-1.6,4.4-2.6,8.8-3.4,13.7c0,0.5-0.3,1.3-0.3,1.8v1.6l-0.3,3.1v0.8v1v4.7
|
||||||
|
c0,33.1-0.5,65.7-0.8,98.6C2.3,238.6,1.8,302.8,1,364.9c-0.3,31.1-0.5,61.6-0.8,91.6C0,471.5,0,486.2,0,501
|
||||||
|
c0,8.5,1.8,17.3,4.9,25.4c3.1,8,7.8,15.3,13.5,21.5c5.7,6.2,12.4,11.4,19.7,15.3l5.7,2.6c2.1,0.8,3.9,1.3,6,2.1
|
||||||
|
c3.9,1,7.8,2.1,12.4,2.6c1,0.3,2.3,0.3,3.1,0.5h2.6h2.6h0.8c1.3,0,0.8,0,1.3,0h1.3c3.4,0,6.7,0,10.4,0c6.7,0,13.7,0,20.2-0.3
|
||||||
|
c26.9-0.3,52.8-0.5,77.6-0.8c49.7-0.5,95.2-1,135.6-1.6c40.4-0.5,75.6-0.8,104.5-0.8c58-0.3,91.1-0.5,91.1-0.5s2.3,0,6.7-0.3
|
||||||
|
c4.4-0.8,11.1-1.6,19.1-4.9s17.3-9.3,25.4-18.9c3.9-4.9,7.8-10.6,10.6-17.1c0.5-1.8,1.3-3.4,1.8-5.2c0.5-1.8,1-3.6,1.6-5.4
|
||||||
|
c0.5-1.8,0.8-3.9,1-6c0.3-1,0.3-2.1,0.5-3.1c0-0.8,0-1.8,0-2.6s0-1.8,0.3-2.8v-1v-2.1c0-1.8,0-3.6,0-5.7c0-3.9,0-7.8,0-11.6
|
||||||
|
c0-8,0-16.3,0-25.1c0-17.6,0.3-36.5,0.3-56.7c0.3-40.4,0.8-85.9,1-135.6c0-7.5,0.3-15.3,0.3-23l24.6-14.2l58.7-34.2l29.2-17.1
|
||||||
|
l3.6-2.1c1.3-0.8,3.1-2.1,4.7-3.1c2.8-2.3,5.7-4.9,8-7.8c4.7-5.7,7.8-12.9,9.1-20.2C720.4,134.6,719.9,127.1,717.3,120.1z
|
||||||
|
M368.2,265.5l-111.5,66c-8.5-11.1-17.1-22.3-25.6-33.1l-29.5-38.3l-7.5-9.3c-0.8-0.8-1-1.6-2.1-2.6c-0.8-1-1.6-2.1-2.6-2.8
|
||||||
|
l-2.8-2.6c-1-0.8-2.1-1.6-3.1-2.3c-8.5-6-19.1-8.8-29.2-7.5c-5.2,0.5-10.4,2.1-15,4.4c-1.3,0.8-2.6,1.3-3.9,2.1
|
||||||
|
c-0.8,0.5-1,0.8-1.3,1l-1.3,0.8l-0.5,0.5l-0.5,0.5l-0.5,0.5l-1.6,1.3c-1,0.8-1.8,1.8-2.8,2.8c-7.2,7.8-11.4,17.9-11.6,28.5
|
||||||
|
c0,5.2,0.8,10.6,2.6,15.5c0.3,1.3,1,2.6,1.6,3.6l0.8,1.8l1,2.1l1,1.8c0.5,0.8,0.5,0.8,1,1.3l0.8,1.3l0.3,0.3v0.3l0.5,0.5l0.5,0.5
|
||||||
|
l15,18.9l30,37.5l30.3,37.5l7.5,9.3l3.9,4.7c1.6,2.1,3.4,3.9,5.4,5.7c4.1,3.6,8.8,6.5,13.7,8.3c2.6,1,5.2,1.6,7.8,2.1
|
||||||
|
c1.3,0.3,2.6,0.5,4.1,0.5h1c0.3,0,0.8,0,1,0h1.6h0.3c7.5,0,15.3-2.1,21.7-5.7l14.8-8.5l29.2-17.1l58.7-34.2L488.6,295l7.5-4.4
|
||||||
|
L494,482.9c-5.2,0-10.4,0-15.5,0l-26.7,0.3l-53.6,0.5l-106.9,1L84.6,487c0.5-32.3,0.8-64.7,1.3-97l0.8-104.3l0.5-52v-52.3l0.3-94.2
|
||||||
|
h43.5l53.6-0.3l106.9-0.3l106.9-1l53.6-0.8c15.5-0.3,30.8-0.5,46.3-0.8l-1,105.1l-12.4,7.2L368.2,265.5z"/>
|
||||||
|
<path id="XMLID_28_" class="st0" d="M565.4,498.4c0-1.8,0-3.6,0-5.7c0-3.9,0-7.8,0-11.6c0-8,0-16.3,0-25.1
|
||||||
|
c0-17.6-0.3-36.5-0.5-56.7c-0.3-40.4-0.8-85.9-1-135.6c0-4.1,0-8.3,0-12.4l-48.7,28.5l2.3,214.3c0,6.2-4.9,11.4-11.1,11.4l0,0h-0.3
|
||||||
|
c-8.8,0-17.9,0-26.7,0l-26.7-0.3l-53.6-0.5L78.4,501c-3.9,0-7-3.1-7-7l0,0c-0.5-34.7-1-69.6-1.3-104.3l-0.8-104.3l-0.3-52v-52
|
||||||
|
L68.8,77.1c0-5.4,4.4-9.8,9.8-10.1l0,0h53.6l53.6,0.3l106.9,0.3l106.9,1l53.6,0.8c17.9,0.3,35.7,0.5,53.6,0.8l0,0c3.9,0,7,3.1,7,7
|
||||||
|
l1,102.5l49.2-28.7c0-15.5,0-31.6-0.3-47.6c0-7.2,0-14.2,0-21.5c0-3.6,0-7.2,0-10.9c0-1.8-0.3-3.1-0.3-4.7c0-1.6-0.5-3.1-0.5-4.4
|
||||||
|
c-2.1-11.9-8.8-23.3-18.9-30.8c-4.9-3.9-10.6-6.7-16.6-8.5c-1.6-0.3-3.1-0.8-4.4-1c-1.6-0.3-2.6-0.5-4.4-0.8l-5.7-0.3h-5.2
|
||||||
|
c-15.3,0-30.5-0.3-46.1-0.3c-62.1-0.3-126.3-1.3-191.5-1.8c-65.2-0.8-131.5-1-197.7-1c-14.8,0-29,6.5-38.8,17.3
|
||||||
|
c-4.9,5.4-8.8,11.6-11.1,18.6c-1.3,3.4-2.1,7-2.3,10.4c0,0.5-0.3,0.8-0.3,1.3v1.6l-0.3,3.9c0,0.3,0,0.3,0,0.3l0,0v1.6v3.1
|
||||||
|
c0,33.1,0.3,66,0.8,98.6c0.8,65.2,1.3,129.4,2.1,191.5c0.3,31.1,0.5,61.6,0.8,91.6c0.3,15,0.3,29.8,0.5,44.3
|
||||||
|
c0,11.9,4.7,23.3,12.4,32.1c3.9,4.4,8.3,8,13.5,10.9l3.9,1.8l3.9,1.6c2.6,0.8,5.4,1.6,8,1.8c0.5,0,1.3,0.3,1.8,0.3l2.6,0.3l2.6,0.3
|
||||||
|
H73h0.3h1.3c3.4,0,6.7,0,10.4,0c6.7,0,13.5,0.3,20.2,0.3c26.9,0.3,52.8,0.5,77.6,0.8c49.7,0.5,95.2,1,135.6,1.6
|
||||||
|
c40.4,0.5,75.6,0.8,104.5,0.8c58,0.3,91.1,0.3,91.1,0.3s1.8,0,5.4-0.3c3.4-0.5,8.8-1,15-3.9c6.2-2.6,13.7-7.2,20.2-14.8
|
||||||
|
c3.1-3.9,6-8.3,8.3-13.7c0.5-1.3,1-2.6,1.6-4.1c0.5-1.3,0.8-2.8,1.3-4.4c0.5-1.6,0.5-2.8,0.8-4.4c0-0.8,0.3-1.3,0.3-2.1
|
||||||
|
s0-1.8,0-2.6s0-1.8,0-2.8v-0.3l0,0v-0.8L565.4,498.4z"/>
|
||||||
|
<path id="XMLID_29_" class="st1" d="M691.5,117.5c-5.7-5.2-14.5-7.2-22-4.9c-0.5,0-0.8,0.3-1.3,0.5c-0.3,0.3-0.8,0.3-1,0.3l-1.8,1
|
||||||
|
l-1,0.5l0,0l0,0l-0.5,0.3l-7.2,4.1l-14.8,8.5l-29.5,16.8l-117.7,67.5l-118,67.3l-118,67.3c-3.1,1.8-7,1-9.1-1.6l0,0l0,0
|
||||||
|
c-10.4-12.2-20.7-24.3-31.1-36.7l-31.1-36.7l-7.8-9.3c-0.5-0.8-1.3-1.6-1.8-2.1l-1.6-1.6l-1.6-1.3c-0.5-0.5-1.3-0.8-1.8-1.3
|
||||||
|
c-4.7-3.1-10.6-4.4-16.3-3.9c-2.8,0.3-5.4,1.3-8,2.6c-0.5,0.3-1,0.5-1.6,0.8c-0.3,0-0.8,0.5-1,0.8l-1.3,0.8l-0.3,0.3l0,0l0,0
|
||||||
|
l-0.3,0.3l-0.8,0.8c-0.5,0.5-1,1-1.6,1.6c-3.6,4.1-5.7,9.6-5.7,15.3c0,2.8,0.5,5.4,1.6,8c0.3,0.8,0.5,1.3,0.8,1.8l0.5,1l0.5,0.8
|
||||||
|
l0.3,0.8c0,0.3,0.5,0.8,0.8,1l0.8,1.3l0.3,0.3l0.5,0.5l15.3,18.6l30.5,37.3l30.3,37.3l7.5,9.3l3.9,4.7c1,1,1.8,2.1,2.8,2.8
|
||||||
|
c2.1,1.8,4.4,3.1,6.7,3.9c1.3,0.5,2.6,0.8,3.9,1c0.5,0,1.3,0.3,1.8,0.3h0.3c0,0,0.3,0,0.5,0h1.3c3.6,0,7.5-1,10.6-2.8l14.5-8.3
|
||||||
|
l29.5-17.1l58.7-33.9l117.5-68.1l117.5-68.1l58.7-33.9l29.5-16.8l3.6-2.1c1-0.8,1.8-1,2.6-1.6c1.6-1.3,3.1-2.6,4.1-3.9
|
||||||
|
c2.6-2.8,4.1-6.7,4.9-10.6c0.5-3.9,0.3-8-1-11.4C696.6,123.4,694.3,120.1,691.5,117.5z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.7 KiB |
19050
Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.dxf
Normal file
BIN
Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.eps
Normal file
BIN
Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
50
Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.svg
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 720 570.7" style="enable-background:new 0 0 720 570.7;" xml:space="preserve">
|
||||||
|
<path id="XMLID_42_" d="M717.1,120l-1.3-2.8l-0.3-0.8l-0.3-0.5l-0.5-1l-0.5-1l-0.3-0.5V113c-0.5-1-0.3-0.5-0.5-0.8l-1.6-2.3
|
||||||
|
c-1-1.6-2.3-2.8-3.4-4.4c-1.3-1.3-2.6-2.6-3.9-3.9c-11.1-9.8-27.4-13.2-41.4-8.8c-1.8,0.5-3.9,1.3-5.7,2.1l-1.8,1l-1,0.5l-1,0.5
|
||||||
|
l-0.5,0.3l-7.2,4.4l-14.7,8.5L602,127.3l-20.2,11.6c0-11.9,0-24.1,0.3-36.2c0-7.2,0-14.2,0-21.5c0-3.6,0-7.2,0-10.9
|
||||||
|
c0-1.8-0.3-4.1-0.3-6.2c0-2.1-0.5-4.1-0.8-6.2c-2.8-16.8-12.2-32.6-26.1-43.2c-7-5.4-14.7-9.3-23.3-11.9c-2.1-0.5-4.1-1-6.5-1.6
|
||||||
|
c-2.3-0.3-4.9-0.8-7-1L512.5,0h-6.2c-15.3,0-30.5,0.3-46,0.3C398.1,0.5,334,1.6,268.8,2.1c-65.2,0.8-131.4,1-197.6,1
|
||||||
|
c-18.6-0.3-37,8-49.4,21.5c-6.2,6.7-11.1,14.7-14.2,23.5c-1.6,4.4-2.6,8.8-3.1,13.7c0,0.5-0.3,1.3-0.3,1.8v1.6l-0.3,3.1v0.8v1v1.6
|
||||||
|
v3.1c0,33.1-0.3,66-0.8,98.6C2.3,238.5,1.8,302.7,1,364.8c-0.3,31-0.5,61.6-0.8,91.6c0,15-0.3,29.7-0.3,44.5
|
||||||
|
c0,8.8,1.8,17.3,4.9,25.4c3.1,8,7.8,15.3,13.5,21.5c5.7,6.2,12.4,11.4,19.7,15.3l5.7,2.6c1.8,0.8,3.9,1.3,5.9,2.1
|
||||||
|
c3.9,1,7.8,2.1,12.4,2.6c1,0.3,2.3,0.3,3.1,0.5h2.6h2.6h0.5c1.3,0,0.8,0,1.3,0h1.3c3.4,0,6.7,0,10.3,0c6.7,0,13.5-0.3,20.2-0.3
|
||||||
|
c26.9-0.3,52.8-0.5,77.6-0.8c49.7-0.5,95.2-1,135.6-1.6c40.4-0.5,75.5-0.8,104.5-0.8c57.9-0.3,91.1-0.3,91.1-0.3s2.3,0,6.7-0.3
|
||||||
|
c4.4-0.5,11.1-1.6,19.1-4.9s17.3-9.3,25.6-18.6c3.9-4.9,7.8-10.6,10.6-17.1c0.5-1.8,1.3-3.4,1.8-5.2c0.5-1.8,1-3.6,1.6-5.4
|
||||||
|
c0.5-1.8,0.8-3.9,1-5.9c0.3-1,0.3-2.1,0.5-3.1c0-0.8,0-1.8,0.3-2.6c0-0.8,0-1.8,0.3-2.8v-1v-0.8V498c0-1.8,0-3.6,0-5.7
|
||||||
|
c0-3.9,0-7.8,0-11.6c0-8,0-16.3,0-25.1c0-17.6,0.3-36.5,0.5-56.7c0.3-40.4,0.8-85.9,1-135.6c0-7.5,0-15.3,0.3-23l24.6-14.2
|
||||||
|
l58.7-34.1l29.2-17.1l3.6-2.1c1.3-0.8,3.1-1.8,4.7-3.1c2.8-2.3,5.7-4.9,8-7.8c4.7-5.7,7.8-12.9,9.1-20.2
|
||||||
|
C720.2,134.5,719.7,127,717.1,120z M563.7,263.6c0.5,49.7,0.8,95.2,1,135.6c0.3,20.2,0.3,39.1,0.5,56.7c0,8.8,0,17.1,0,25.1
|
||||||
|
c0,3.9,0,8,0,11.6c0,1.8,0,3.9,0,5.7v1.3v0.8l0,0v0.3c0,1,0,1.8,0,2.8c0,0.8,0,1.8,0,2.6c0,0.8-0.3,1.6-0.3,2.1
|
||||||
|
c-0.3,1.6-0.3,2.8-0.8,4.4c-0.5,1.6-0.8,2.8-1.3,4.4c-0.5,1.3-1,2.8-1.6,4.1c-2.3,5.2-5.2,9.8-8.3,13.7
|
||||||
|
c-6.5,7.5-13.7,12.2-20.2,14.7c-6.2,2.6-11.6,3.4-15,3.9c-3.6,0.3-5.4,0.3-5.4,0.3s-33.1-0.3-91.1-0.3c-29,0-64.2-0.3-104.5-0.8
|
||||||
|
c-40.4-0.5-85.9-1-135.6-1.6c-24.8-0.3-50.7-0.5-77.6-0.8c-6.7,0-13.5-0.3-20.2-0.3c-3.4,0-6.7,0-10.3,0h-1.3c0,0,0,0-0.3,0h-0.5
|
||||||
|
l-2.6-0.3l-2.6-0.3c-0.8,0-1.3-0.3-1.8-0.3c-2.3-0.3-5.2-1-8-1.8l-3.9-1.6l-3.9-1.8c-4.9-2.8-9.6-6.5-13.5-10.9
|
||||||
|
c-7.5-8.8-12.2-20.2-12.4-32.1c-0.3-14.5-0.3-29.2-0.5-44.2c-0.3-30-0.5-60.5-0.8-91.6c-0.8-62.1-1.3-126.2-2.1-191.4
|
||||||
|
c-0.3-32.6-0.5-65.4-0.8-98.6v-3.1v-1.6l0,0c0,0,0,0,0-0.3l0.3-3.9v-1.6c0-0.5,0.3-0.8,0.3-1.3c0.5-3.1,1.3-6.7,2.3-10.3
|
||||||
|
c2.3-6.7,6.2-13.2,11.1-18.6c9.8-10.9,24.1-17.3,38.8-17.3c66.2,0,132.5,0.3,197.6,1c65.2,0.5,129.3,1.3,191.4,1.8
|
||||||
|
c15.5,0,31,0.3,46,0.3h5.2l5.7,0.3c1.6,0.3,2.8,0.5,4.4,0.8c1.6,0.3,3.1,0.8,4.4,1c5.9,1.8,11.6,4.7,16.6,8.5
|
||||||
|
c9.8,7.5,16.6,18.9,18.9,30.8c0.3,1.6,0.5,3.1,0.5,4.4c0,1.6,0.3,2.8,0.3,4.7c0,3.6,0,7.2,0,10.9c0,7.2,0,14.5,0,21.5
|
||||||
|
c0,16,0,32.1,0.3,47.6l-49.2,28.7l-1-102.4c0-3.9-3.1-7-7-7l0,0c-17.8-0.3-35.7-0.5-53.5-0.8l-53.5-0.8l-106.8-1l-106.8-0.3
|
||||||
|
L130.9,67H77.3l0,0c-5.4,0-9.8,4.4-9.8,10.1l0.3,104.3v52l0.3,52l0.8,104.3c0.5,34.7,0.8,69.6,1.3,104.3l0,0c0,3.9,3.1,7,7,7
|
||||||
|
l320.8,3.6l53.5,0.5l26.6,0.3c8.8,0,17.8,0,26.6,0h0.3l0,0c6.2,0,11.1-5.2,11.1-11.4l-2.3-214.2l48.6-28.5
|
||||||
|
C563.4,255.3,563.4,259.5,563.7,263.6z M256.6,331.1c-8.5-11.1-17.1-22.2-25.6-33.1l-29.5-38l-7.5-9.3c-0.8-0.8-1-1.6-2.1-2.3
|
||||||
|
c-0.8-1-1.6-1.8-2.6-2.8l-2.8-2.6c-1-0.8-2.1-1.6-3.1-2.3c-8.5-5.9-19.1-8.8-29.2-7.5c-5.2,0.5-10.3,2.1-15,4.4
|
||||||
|
c-1.3,0.8-2.6,1.3-3.9,2.1c-0.8,0.5-1,0.5-1.3,1l-1.3,0.8l-0.5,0.5l-0.5,0.5l-0.3,0.3l-0.3,0.3l-1.6,1.3c-1,0.8-1.8,1.8-2.8,2.8
|
||||||
|
c-7.2,7.8-11.4,17.8-11.6,28.5c0,5.2,0.8,10.6,2.3,15.5c0.3,1.3,1,2.6,1.6,3.6l0.8,1.8l1,1.8l1,1.8c0.5,0.8,0.5,0.8,1,1.3l0.8,1.3
|
||||||
|
l0.3,0.3v0.3l0.5,0.5l0.5,0.5l15,18.9l30,37.5l30,37.5l7.5,9.3l3.9,4.7c1.6,1.8,3.4,3.9,5.4,5.7c4.1,3.6,8.8,6.5,13.7,8.3
|
||||||
|
c2.6,1,5.2,1.6,7.8,2.1c1.3,0.3,2.6,0.5,4.1,0.5h1c0.5,0,0.8,0,1,0h1.6h0.3c7.5,0,15.3-2.1,21.7-5.7l14.7-8.5l29.2-17.1l58.7-34.1
|
||||||
|
l117.4-68.3l7.5-4.4l-2.1,192.2c-5.2,0-10.3,0-15.5,0l-26.6,0.3l-53.5,0.5L291,484.8l-206.7,2.3c0.5-32.3,0.8-64.7,1.3-97l0.8-104.3
|
||||||
|
l0.3-52v-52l0.3-94.2h43.5l53.5-0.3l106.8-0.3l106.8-1l53.5-0.8c15.5-0.3,30.8-0.5,46.3-0.8l-1,105l-12.4,7.2l-116.9,69.1
|
||||||
|
L256.6,331.1z M698.7,138.7c-0.8,3.9-2.3,7.5-4.9,10.6c-1.3,1.6-2.6,2.8-4.1,3.9c-0.8,0.5-1.6,1-2.6,1.6l-3.6,2.1L654,173.6
|
||||||
|
l-58.7,33.9l-117.4,68.3l-117.4,68l-58.7,33.9l-29.5,17.1l-14.5,8.3c-3.4,1.8-7,2.8-10.6,2.8h-1.3c-0.3,0-0.5,0-0.5,0H245
|
||||||
|
c-0.5,0-1.3-0.3-1.8-0.3c-1.3-0.3-2.6-0.5-3.9-1c-2.6-0.8-4.7-2.3-6.7-3.9c-1-1-1.8-1.8-2.8-2.8l-3.9-4.7l-7.5-9.3l-30.3-37.3
|
||||||
|
l-30.8-37.5L142,290.5l-0.5-0.5l-0.3-0.3l-0.8-1.3c-0.3-0.3-0.8-0.8-0.8-1l-0.3-0.8l-0.5-0.8l-0.5-1c-0.3-0.5-0.5-1.3-0.8-1.8
|
||||||
|
c-1-2.6-1.6-5.2-1.6-8c0-5.4,2.1-11.1,5.7-15.3c0.5-0.5,1-1,1.6-1.6l0.8-0.8l0.3-0.3l0,0l0,0l0.3-0.3l1.3-0.8c0.3-0.3,0.8-0.8,1-0.8
|
||||||
|
c0.5-0.3,1-0.5,1.6-0.8c2.6-1.3,5.2-2.1,8-2.6c5.7-0.8,11.6,0.5,16.3,3.9c0.5,0.5,1.3,0.8,1.8,1.3l1.6,1.3l1.6,1.6
|
||||||
|
c0.5,0.5,1.3,1.6,1.8,2.1l7.8,9.3l31,36.7c10.3,12.2,20.7,24.6,31,36.7l0,0l0,0c2.1,2.6,5.9,3.4,9.1,1.6l118-67.3l118-67.3
|
||||||
|
l117.7-67.5l29.5-16.8l14.7-8.5l7.2-4.1l0.5-0.3l0,0l0,0l1-0.5l1.8-1c0.3,0,0.8-0.3,1-0.3c0.3-0.3,0.8-0.3,1.3-0.5
|
||||||
|
c7.5-2.3,16.3-0.5,22,4.9c2.8,2.6,5.2,5.9,6.5,9.8C699,130.6,699.2,134.8,698.7,138.7z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.6 KiB |
6342
Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 03.dxf
Normal file
27
Printing/Nameplates/Check-Mark/index.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /Printing/Nameplates/Check-Mark/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /Printing/Nameplates/Check-Mark/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href="CF00089-08%20Check%20Mark%2001.dxf">CF00089-08 Check Mark 01.dxf</a></li>
|
||||||
|
<li><a href="CF00089-08%20Check%20Mark%2001.eps">CF00089-08 Check Mark 01.eps</a></li>
|
||||||
|
<li><a href="CF00089-08%20Check%20Mark%2001.png">CF00089-08 Check Mark 01.png</a></li>
|
||||||
|
<li><a href="CF00089-08%20Check%20Mark%2001.svg">CF00089-08 Check Mark 01.svg</a></li>
|
||||||
|
<li><a href="CF00089-08%20Check%20Mark%2002.dxf">CF00089-08 Check Mark 02.dxf</a></li>
|
||||||
|
<li><a href="CF00089-08%20Check%20Mark%2002.eps">CF00089-08 Check Mark 02.eps</a></li>
|
||||||
|
<li><a href="CF00089-08%20Check%20Mark%2002.png">CF00089-08 Check Mark 02.png</a></li>
|
||||||
|
<li><a href="CF00089-08%20Check%20Mark%2002.svg">CF00089-08 Check Mark 02.svg</a></li>
|
||||||
|
<li><a href="CF00089-08%20Check%20Mark%2003.dxf">CF00089-08 Check Mark 03.dxf</a></li>
|
||||||
|
<li><a href="CF00089-08%20Check%20Mark%2003.eps">CF00089-08 Check Mark 03.eps</a></li>
|
||||||
|
<li><a href="CF00089-08%20Check%20Mark%2003.png">CF00089-08 Check Mark 03.png</a></li>
|
||||||
|
<li><a href="CF00089-08%20Check%20Mark%2003.svg">CF00089-08 Check Mark 03.svg</a></li>
|
||||||
|
<li><a href="Note.txt">Note.txt</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
Printing/Nameplates/Fordscript.ttf
Normal file
BIN
Printing/Nameplates/Screenshot 2025-11-07 082805.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
174
Printing/Nameplates/add_filament_change_to_3mf.py
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Add M600 filament change command to all objects in an Orca Slicer 3MF file.
|
||||||
|
|
||||||
|
This script modifies a 3MF file (which is a ZIP archive containing XML) to add
|
||||||
|
a height range modifier at 1.5mm (end of blue base layer) with M600 command
|
||||||
|
for all zipper pull objects.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python add_filament_change_to_3mf.py input.3mf [output.3mf]
|
||||||
|
|
||||||
|
If output.3mf is not specified, creates input_modified.3mf
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import zipfile
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def add_height_range_to_3mf(input_file, output_file=None, height_mm=1.5):
|
||||||
|
"""
|
||||||
|
Add height range modifier with M600 at specified height to all objects in 3MF.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_file: Path to input 3MF file
|
||||||
|
output_file: Path to output 3MF file (optional)
|
||||||
|
height_mm: Height in mm where filament change occurs (default: 1.5)
|
||||||
|
"""
|
||||||
|
input_path = Path(input_file)
|
||||||
|
|
||||||
|
if not input_path.exists():
|
||||||
|
print(f"Error: Input file '{input_file}' not found!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Determine output filename
|
||||||
|
if output_file is None:
|
||||||
|
output_path = input_path.parent / f"{input_path.stem}_modified.3mf"
|
||||||
|
else:
|
||||||
|
output_path = Path(output_file)
|
||||||
|
|
||||||
|
print(f"Processing: {input_path}")
|
||||||
|
print(f"Output will be: {output_path}")
|
||||||
|
print(f"Filament change height: {height_mm}mm")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
# Create a temporary directory to work in
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
temp_path = Path(temp_dir)
|
||||||
|
|
||||||
|
# Extract the 3MF (it's a ZIP file)
|
||||||
|
print("Extracting 3MF file...")
|
||||||
|
with zipfile.ZipFile(input_path, 'r') as zip_ref:
|
||||||
|
zip_ref.extractall(temp_path)
|
||||||
|
|
||||||
|
# Find and modify the 3D model file (usually 3D/3dmodel.model)
|
||||||
|
model_file = temp_path / "3D" / "3dmodel.model"
|
||||||
|
|
||||||
|
if not model_file.exists():
|
||||||
|
print("Error: Could not find 3dmodel.model in 3MF file!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"Modifying {model_file}...")
|
||||||
|
|
||||||
|
# Parse the XML
|
||||||
|
tree = ET.parse(model_file)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
# Define namespaces (3MF uses namespaces)
|
||||||
|
namespaces = {
|
||||||
|
'': 'http://schemas.microsoft.com/3dmanufacturing/core/2015/02',
|
||||||
|
'p': 'http://schemas.microsoft.com/3dmanufacturing/production/2015/06',
|
||||||
|
's': 'http://schemas.orca-3d.com/3mf/2023/06'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Register namespaces for output
|
||||||
|
for prefix, uri in namespaces.items():
|
||||||
|
if prefix:
|
||||||
|
ET.register_namespace(prefix, uri)
|
||||||
|
else:
|
||||||
|
ET.register_namespace('', uri)
|
||||||
|
|
||||||
|
# Find all objects (build items)
|
||||||
|
# In Orca Slicer 3MF, objects are in <build><item> tags
|
||||||
|
build_elem = root.find('.//build', namespaces)
|
||||||
|
|
||||||
|
if build_elem is None:
|
||||||
|
print("Warning: No <build> element found. Looking for items directly...")
|
||||||
|
items = root.findall('.//item', namespaces)
|
||||||
|
else:
|
||||||
|
items = build_elem.findall('.//item', namespaces)
|
||||||
|
|
||||||
|
if not items:
|
||||||
|
print("Error: No items found in 3MF file!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"Found {len(items)} object(s) in the file")
|
||||||
|
|
||||||
|
# Count modified items
|
||||||
|
modified_count = 0
|
||||||
|
|
||||||
|
# Add height range modifier to each item
|
||||||
|
for idx, item in enumerate(items, 1):
|
||||||
|
object_id = item.get('objectid', f'unknown_{idx}')
|
||||||
|
print(f" Processing object {idx}/{len(items)} (ID: {object_id})...")
|
||||||
|
|
||||||
|
# Check if this item already has metadata for height range
|
||||||
|
# In Orca Slicer, this is typically stored as metadata
|
||||||
|
# We need to add the height range modifier metadata
|
||||||
|
|
||||||
|
# Note: The exact XML structure for height range modifiers in Orca Slicer
|
||||||
|
# may vary. This is a generic approach that adds metadata.
|
||||||
|
# You may need to adjust based on actual Orca Slicer 3MF structure.
|
||||||
|
|
||||||
|
# Create or find metadata container
|
||||||
|
metadata_group = item.find('metadatagroup', namespaces)
|
||||||
|
if metadata_group is None:
|
||||||
|
metadata_group = ET.SubElement(item, 'metadatagroup')
|
||||||
|
|
||||||
|
# Add height range modifier metadata
|
||||||
|
# Format: height range from 0 to height_mm = blue (original)
|
||||||
|
# height range from height_mm to top = white (with M600)
|
||||||
|
|
||||||
|
height_modifier = ET.SubElement(metadata_group, 'metadata')
|
||||||
|
height_modifier.set('name', 'height_range_modifier')
|
||||||
|
height_modifier.text = f'{{"ranges":[{{"min":0,"max":{height_mm},"color":"RoyalBlue"}},{{"min":{height_mm},"max":999,"color":"white","gcode":"M600"}}]}}'
|
||||||
|
|
||||||
|
modified_count += 1
|
||||||
|
|
||||||
|
print(f"\nModified {modified_count} object(s)")
|
||||||
|
|
||||||
|
# Save the modified XML back to the file
|
||||||
|
print("Saving modified model file...")
|
||||||
|
tree.write(model_file, encoding='utf-8', xml_declaration=True)
|
||||||
|
|
||||||
|
# Re-create the 3MF (ZIP) file with all contents
|
||||||
|
print("Creating new 3MF file...")
|
||||||
|
with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zip_out:
|
||||||
|
# Walk through the temp directory and add all files
|
||||||
|
for root_dir, dirs, files in os.walk(temp_path):
|
||||||
|
for file in files:
|
||||||
|
file_path = Path(root_dir) / file
|
||||||
|
arcname = file_path.relative_to(temp_path)
|
||||||
|
zip_out.write(file_path, arcname)
|
||||||
|
|
||||||
|
print(f"\nSuccess! Modified 3MF saved to: {output_path}")
|
||||||
|
print(f"\nNext steps:")
|
||||||
|
print(f"1. Open {output_path.name} in Orca Slicer")
|
||||||
|
print(f"2. Verify the height range modifiers are present")
|
||||||
|
print(f"3. Slice and check for M600 commands in the G-code")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: python add_filament_change_to_3mf.py input.3mf [output.3mf]")
|
||||||
|
print("\nAdds M600 filament change at 1.5mm to all objects in a 3MF file.")
|
||||||
|
print("If output.3mf is not specified, creates input_modified.3mf")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
input_file = sys.argv[1]
|
||||||
|
output_file = sys.argv[2] if len(sys.argv) > 2 else None
|
||||||
|
|
||||||
|
success = add_height_range_to_3mf(input_file, output_file)
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
179
Printing/Nameplates/add_height_modifier_orca.py
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Add M600 filament change to Orca Slicer 3MF file.
|
||||||
|
|
||||||
|
Based on actual Orca Slicer format analysis.
|
||||||
|
Adds global height_range_modifier metadata that applies to all objects.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python add_height_modifier_orca.py input.3mf [output.3mf]
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import zipfile
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def add_height_modifier(input_file, output_file=None, height_mm=1.5):
|
||||||
|
"""
|
||||||
|
Add height range modifier metadata to 3MF file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_file: Path to input 3MF file
|
||||||
|
output_file: Path to output 3MF file (optional)
|
||||||
|
height_mm: Height in mm where filament change occurs
|
||||||
|
"""
|
||||||
|
input_path = Path(input_file)
|
||||||
|
|
||||||
|
if not input_path.exists():
|
||||||
|
print(f"Error: Input file '{input_file}' not found!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if output_file is None:
|
||||||
|
output_path = input_path.parent / f"{input_path.stem}_modified.3mf"
|
||||||
|
else:
|
||||||
|
output_path = Path(output_file)
|
||||||
|
|
||||||
|
print(f"Processing: {input_path}")
|
||||||
|
print(f"Output: {output_path}")
|
||||||
|
print(f"Filament change at: {height_mm}mm")
|
||||||
|
print("-" * 60)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
temp_path = Path(temp_dir)
|
||||||
|
|
||||||
|
# Extract 3MF
|
||||||
|
print("Extracting 3MF...")
|
||||||
|
with zipfile.ZipFile(input_path, 'r') as zip_ref:
|
||||||
|
zip_ref.extractall(temp_path)
|
||||||
|
|
||||||
|
# Find model file
|
||||||
|
model_file = temp_path / "3D" / "3dmodel.model"
|
||||||
|
if not model_file.exists():
|
||||||
|
print("Error: Could not find 3dmodel.model!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("Modifying model file...")
|
||||||
|
|
||||||
|
# Parse XML
|
||||||
|
tree = ET.parse(model_file)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
# Register namespaces
|
||||||
|
namespaces = {
|
||||||
|
'': 'http://schemas.microsoft.com/3dmanufacturing/core/2015/02',
|
||||||
|
'BambuStudio': 'http://schemas.bambulab.com/package/2021',
|
||||||
|
'p': 'http://schemas.microsoft.com/3dmanufacturing/production/2015/06'
|
||||||
|
}
|
||||||
|
|
||||||
|
for prefix, uri in namespaces.items():
|
||||||
|
if prefix:
|
||||||
|
ET.register_namespace(prefix, uri)
|
||||||
|
else:
|
||||||
|
ET.register_namespace('', uri)
|
||||||
|
|
||||||
|
# Find or create metadata section (should be at root level, before <resources>)
|
||||||
|
# Check if height_range_modifier already exists
|
||||||
|
existing_modifier = None
|
||||||
|
for metadata in root.findall('.//metadata[@name="height_range_modifier"]', namespaces):
|
||||||
|
existing_modifier = metadata
|
||||||
|
break
|
||||||
|
|
||||||
|
if existing_modifier is not None:
|
||||||
|
print(" Found existing height_range_modifier, replacing...")
|
||||||
|
root.remove(existing_modifier)
|
||||||
|
|
||||||
|
# Create the height range modifier metadata
|
||||||
|
# Format: JSON string - we'll manually insert it to avoid auto-escaping
|
||||||
|
metadata_json = (
|
||||||
|
'{"ranges":['
|
||||||
|
f'{{"min":0,"max":{height_mm},"color":"RoyalBlue"}},'
|
||||||
|
f'{{"min":{height_mm},"max":999,"color":"white","gcode":"M600"}}'
|
||||||
|
']}'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create metadata element - we'll manually set the text with proper escaping
|
||||||
|
metadata_elem = ET.Element('metadata')
|
||||||
|
metadata_elem.set('name', 'height_range_modifier')
|
||||||
|
# Don't use .text = ... as it will auto-escape
|
||||||
|
# We'll replace this after writing
|
||||||
|
|
||||||
|
# Insert after the last existing metadata element (before <resources>)
|
||||||
|
resources = root.find('.//resources', namespaces)
|
||||||
|
if resources is not None:
|
||||||
|
resources_index = list(root).index(resources)
|
||||||
|
root.insert(resources_index, metadata_elem)
|
||||||
|
else:
|
||||||
|
# No resources found, just append
|
||||||
|
root.append(metadata_elem)
|
||||||
|
|
||||||
|
print(f" Added height_range_modifier metadata")
|
||||||
|
print(f" Blue layer: 0mm to {height_mm}mm")
|
||||||
|
print(f" White layer: {height_mm}mm to top (with M600)")
|
||||||
|
|
||||||
|
# Save modified XML
|
||||||
|
tree.write(model_file, encoding='utf-8', xml_declaration=True)
|
||||||
|
|
||||||
|
# Post-process the file to fix the escaping
|
||||||
|
# Python's ET auto-escapes, but we need &quot; not &amp;quot;
|
||||||
|
with open(model_file, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Replace the empty metadata element with our properly escaped version
|
||||||
|
escaped_json = metadata_json.replace('"', '&quot;')
|
||||||
|
content = content.replace(
|
||||||
|
'<metadata name="height_range_modifier" />',
|
||||||
|
f'<metadata name="height_range_modifier">{escaped_json}</metadata>'
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
'<metadata name="height_range_modifier"></metadata>',
|
||||||
|
f'<metadata name="height_range_modifier">{escaped_json}</metadata>'
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(model_file, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
# Re-create 3MF file
|
||||||
|
print("Creating modified 3MF...")
|
||||||
|
with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zip_out:
|
||||||
|
for root_dir, dirs, files in os.walk(temp_path):
|
||||||
|
for file in files:
|
||||||
|
file_path = Path(root_dir) / file
|
||||||
|
arcname = file_path.relative_to(temp_path)
|
||||||
|
zip_out.write(file_path, arcname)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("Success! Modified 3MF saved.")
|
||||||
|
print()
|
||||||
|
print("Next steps:")
|
||||||
|
print("1. Open the modified file in Orca Slicer")
|
||||||
|
print("2. Check if height range modifier appears")
|
||||||
|
print("3. Slice and verify M600 in G-code")
|
||||||
|
print()
|
||||||
|
print("Note: This adds a GLOBAL height modifier that applies to")
|
||||||
|
print(" all objects in the build plate.")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: python add_height_modifier_orca.py input.3mf [output.3mf]")
|
||||||
|
print()
|
||||||
|
print("Adds M600 filament change at 1.5mm (global, applies to all objects)")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
input_file = sys.argv[1]
|
||||||
|
output_file = sys.argv[2] if len(sys.argv) > 2 else None
|
||||||
|
|
||||||
|
success = add_height_modifier(input_file, output_file)
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
56
Printing/Nameplates/checkmark_template.scad
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// Two-Layer Check Mark Design
|
||||||
|
// Black base (box + check mark) + White fill layer on top
|
||||||
|
// Based on CF00089-08 Check Mark 01.svg
|
||||||
|
|
||||||
|
// Parameters - adjust these to customize
|
||||||
|
black_base_thickness = 0.5; // Black base layer (thin, bottom)
|
||||||
|
white_fill_thickness = 1.5; // White fill layer (middle)
|
||||||
|
black_top_thickness = 0.5; // Black top (check mark shows through)
|
||||||
|
desired_size = 14; // Desired box size (mm) - makes design square
|
||||||
|
|
||||||
|
// SVG file path
|
||||||
|
svg_file = "Check-Mark/CF00089-08 Check Mark 01.svg";
|
||||||
|
|
||||||
|
// Calculate dimensions from SVG viewBox (720 x 570.9)
|
||||||
|
svg_width = 720;
|
||||||
|
svg_height = 570.9;
|
||||||
|
|
||||||
|
// Scale factors to make the design exactly square
|
||||||
|
scale_x = desired_size / svg_width; // Scale for width
|
||||||
|
scale_y = desired_size / svg_height; // Scale for height
|
||||||
|
design_width = desired_size;
|
||||||
|
design_height = desired_size;
|
||||||
|
|
||||||
|
// White fill inset (how much smaller than the outer box)
|
||||||
|
white_inset = 1.5; // mm inset from edges (adjusted for 14mm box)
|
||||||
|
white_width = design_width - (white_inset * 2);
|
||||||
|
white_height = design_height - (white_inset * 2);
|
||||||
|
|
||||||
|
// Total thickness
|
||||||
|
total_thickness = black_base_thickness + white_fill_thickness + black_top_thickness;
|
||||||
|
|
||||||
|
echo(str("Design size: ", design_width, "mm x ", design_height, "mm"));
|
||||||
|
echo(str("White fill size: ", white_width, "mm x ", white_height, "mm"));
|
||||||
|
echo(str("Total thickness: ", total_thickness, "mm"));
|
||||||
|
|
||||||
|
// Build the two-layer design
|
||||||
|
module checkmark_design() {
|
||||||
|
// Layer 1: Black base - full SVG design (box + check mark)
|
||||||
|
color("black")
|
||||||
|
translate([0, 0, 0])
|
||||||
|
linear_extrude(height = total_thickness)
|
||||||
|
// Center the SVG design
|
||||||
|
translate([-design_width/2, -design_height/2, 0])
|
||||||
|
scale([scale_x, scale_y, 1])
|
||||||
|
import(svg_file, center = false);
|
||||||
|
|
||||||
|
// Layer 2: White fill - sits on top of black base, inside the box
|
||||||
|
color("white")
|
||||||
|
translate([0, 0, black_base_thickness])
|
||||||
|
linear_extrude(height = white_fill_thickness)
|
||||||
|
offset(r = 1) // Slightly round the corners
|
||||||
|
square([white_width, white_height], center = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the complete design
|
||||||
|
checkmark_design();
|
||||||
BIN
Printing/Nameplates/fred_bennett_facedown.3mf
Normal file
107
Printing/Nameplates/generate_nameplates.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate 3D nameplate STL files from a list of names using OpenSCAD.
|
||||||
|
|
||||||
|
This script reads names from a text file and generates individual STL files
|
||||||
|
for each name using an OpenSCAD template.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def generate_stl(name, template_file, output_dir):
|
||||||
|
"""
|
||||||
|
Generate an STL file for a given name using OpenSCAD.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: The name to put on the nameplate
|
||||||
|
template_file: Path to the OpenSCAD template file
|
||||||
|
output_dir: Directory where STL files will be saved
|
||||||
|
"""
|
||||||
|
# Create a safe filename from the name (remove special characters)
|
||||||
|
safe_name = "".join(c for c in name if c.isalnum() or c in (' ', '-', '_')).strip()
|
||||||
|
safe_name = safe_name.replace(' ', '_')
|
||||||
|
|
||||||
|
output_file = os.path.join(output_dir, f"{safe_name}.stl")
|
||||||
|
|
||||||
|
# Escape quotes in the name for the command line
|
||||||
|
escaped_name = name.replace('"', '\\"')
|
||||||
|
|
||||||
|
# Build the OpenSCAD command
|
||||||
|
# -D sets a variable, -o specifies output file
|
||||||
|
cmd = [
|
||||||
|
r'C:\Program Files\OpenSCAD\openscad.exe',
|
||||||
|
'-D', f'name="{escaped_name}"',
|
||||||
|
'-o', output_file,
|
||||||
|
template_file
|
||||||
|
]
|
||||||
|
|
||||||
|
print(f"Generating: {safe_name}.stl for '{name}'...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run OpenSCAD
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
print(f" [OK] Successfully created {safe_name}.stl")
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f" [ERROR] Error generating {safe_name}.stl")
|
||||||
|
print(f" {e.stderr}")
|
||||||
|
return False
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Error: OpenSCAD not found. Please install OpenSCAD and ensure it's in your PATH.")
|
||||||
|
print("Download from: https://openscad.org/downloads.html")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Configuration
|
||||||
|
template_file = "nameplate_template.scad"
|
||||||
|
names_file = "names.txt"
|
||||||
|
output_dir = "output_stl"
|
||||||
|
|
||||||
|
# Check if template exists
|
||||||
|
if not os.path.exists(template_file):
|
||||||
|
print(f"Error: Template file '{template_file}' not found!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Check if names file exists
|
||||||
|
if not os.path.exists(names_file):
|
||||||
|
print(f"Error: Names file '{names_file}' not found!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Create output directory if it doesn't exist
|
||||||
|
Path(output_dir).mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Read names from file
|
||||||
|
with open(names_file, 'r', encoding='utf-8') as f:
|
||||||
|
names = [line.strip() for line in f if line.strip()]
|
||||||
|
|
||||||
|
if not names:
|
||||||
|
print(f"Error: No names found in '{names_file}'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"Found {len(names)} name(s) to process")
|
||||||
|
print(f"Output directory: {output_dir}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
# Generate STL for each name
|
||||||
|
success_count = 0
|
||||||
|
for name in names:
|
||||||
|
if generate_stl(name, template_file, output_dir):
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
print("-" * 50)
|
||||||
|
print(f"Complete! Generated {success_count}/{len(names)} STL files")
|
||||||
|
print(f"Files saved in: {output_dir}/")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
107
Printing/Nameplates/generate_zipper_pulls.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate 3D zipper pull STL files from a list of names using OpenSCAD.
|
||||||
|
|
||||||
|
This script reads names from a text file and generates individual STL files
|
||||||
|
for each name using the zipper pull OpenSCAD template.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def generate_stl(name, template_file, output_dir):
|
||||||
|
"""
|
||||||
|
Generate an STL file for a given name using OpenSCAD.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: The name to put on the zipper pull
|
||||||
|
template_file: Path to the OpenSCAD template file
|
||||||
|
output_dir: Directory where STL files will be saved
|
||||||
|
"""
|
||||||
|
# Create a safe filename from the name (remove special characters)
|
||||||
|
safe_name = "".join(c for c in name if c.isalnum() or c in (' ', '-', '_')).strip()
|
||||||
|
safe_name = safe_name.replace(' ', '_')
|
||||||
|
|
||||||
|
output_file = os.path.join(output_dir, f"{safe_name}.stl")
|
||||||
|
|
||||||
|
# Escape quotes in the name for the command line
|
||||||
|
escaped_name = name.replace('"', '\\"')
|
||||||
|
|
||||||
|
# Build the OpenSCAD command
|
||||||
|
# -D sets a variable, -o specifies output file
|
||||||
|
cmd = [
|
||||||
|
r'C:\Program Files\OpenSCAD\openscad.exe',
|
||||||
|
'-D', f'name="{escaped_name}"',
|
||||||
|
'-o', output_file,
|
||||||
|
template_file
|
||||||
|
]
|
||||||
|
|
||||||
|
print(f"Generating: {safe_name}.stl for '{name}'...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run OpenSCAD
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
print(f" [OK] Successfully created {safe_name}.stl")
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f" [ERROR] Error generating {safe_name}.stl")
|
||||||
|
print(f" {e.stderr}")
|
||||||
|
return False
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Error: OpenSCAD not found. Please install OpenSCAD and ensure it's in your PATH.")
|
||||||
|
print("Download from: https://openscad.org/downloads.html")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Configuration
|
||||||
|
template_file = "zipper_pull_template.scad"
|
||||||
|
names_file = "names.txt"
|
||||||
|
output_dir = "zipper-pulls"
|
||||||
|
|
||||||
|
# Check if template exists
|
||||||
|
if not os.path.exists(template_file):
|
||||||
|
print(f"Error: Template file '{template_file}' not found!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Check if names file exists
|
||||||
|
if not os.path.exists(names_file):
|
||||||
|
print(f"Error: Names file '{names_file}' not found!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Create output directory if it doesn't exist
|
||||||
|
Path(output_dir).mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Read names from file
|
||||||
|
with open(names_file, 'r', encoding='utf-8') as f:
|
||||||
|
names = [line.strip() for line in f if line.strip()]
|
||||||
|
|
||||||
|
if not names:
|
||||||
|
print(f"Error: No names found in '{names_file}'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"Found {len(names)} name(s) to process")
|
||||||
|
print(f"Output directory: {output_dir}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
# Generate STL for each name
|
||||||
|
success_count = 0
|
||||||
|
for name in names:
|
||||||
|
if generate_stl(name, template_file, output_dir):
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
print("-" * 50)
|
||||||
|
print(f"Complete! Generated {success_count}/{len(names)} STL files")
|
||||||
|
print(f"Files saved in: {output_dir}/")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
107
Printing/Nameplates/generate_zipper_pulls_raised_text.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate 3D zipper pull STL files with raised text from a list of names using OpenSCAD.
|
||||||
|
|
||||||
|
This script reads names from a text file and generates individual STL files
|
||||||
|
for each name using the raised text zipper pull OpenSCAD template.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def generate_stl(name, template_file, output_dir):
|
||||||
|
"""
|
||||||
|
Generate an STL file for a given name using OpenSCAD.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: The name to put on the zipper pull
|
||||||
|
template_file: Path to the OpenSCAD template file
|
||||||
|
output_dir: Directory where STL files will be saved
|
||||||
|
"""
|
||||||
|
# Create a safe filename from the name (remove special characters)
|
||||||
|
safe_name = "".join(c for c in name if c.isalnum() or c in (' ', '-', '_')).strip()
|
||||||
|
safe_name = safe_name.replace(' ', '_')
|
||||||
|
|
||||||
|
output_file = os.path.join(output_dir, f"{safe_name}.stl")
|
||||||
|
|
||||||
|
# Escape quotes in the name for the command line
|
||||||
|
escaped_name = name.replace('"', '\\"')
|
||||||
|
|
||||||
|
# Build the OpenSCAD command
|
||||||
|
# -D sets a variable, -o specifies output file
|
||||||
|
cmd = [
|
||||||
|
r'C:\Program Files\OpenSCAD\openscad.exe',
|
||||||
|
'-D', f'name="{escaped_name}"',
|
||||||
|
'-o', output_file,
|
||||||
|
template_file
|
||||||
|
]
|
||||||
|
|
||||||
|
print(f"Generating: {safe_name}.stl for '{name}'...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run OpenSCAD
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
print(f" [OK] Successfully created {safe_name}.stl")
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f" [ERROR] Error generating {safe_name}.stl")
|
||||||
|
print(f" {e.stderr}")
|
||||||
|
return False
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Error: OpenSCAD not found. Please install OpenSCAD and ensure it's in your PATH.")
|
||||||
|
print("Download from: https://openscad.org/downloads.html")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Configuration
|
||||||
|
template_file = "zipper_pull_raised_text_template.scad"
|
||||||
|
names_file = "names.txt"
|
||||||
|
output_dir = "zipper-pulls-raised-text"
|
||||||
|
|
||||||
|
# Check if template exists
|
||||||
|
if not os.path.exists(template_file):
|
||||||
|
print(f"Error: Template file '{template_file}' not found!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Check if names file exists
|
||||||
|
if not os.path.exists(names_file):
|
||||||
|
print(f"Error: Names file '{names_file}' not found!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Create output directory if it doesn't exist
|
||||||
|
Path(output_dir).mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Read names from file
|
||||||
|
with open(names_file, 'r', encoding='utf-8') as f:
|
||||||
|
names = [line.strip() for line in f if line.strip()]
|
||||||
|
|
||||||
|
if not names:
|
||||||
|
print(f"Error: No names found in '{names_file}'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"Found {len(names)} name(s) to process")
|
||||||
|
print(f"Output directory: {output_dir}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
# Generate STL for each name
|
||||||
|
success_count = 0
|
||||||
|
for name in names:
|
||||||
|
if generate_stl(name, template_file, output_dir):
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
print("-" * 50)
|
||||||
|
print(f"Complete! Generated {success_count}/{len(names)} STL files")
|
||||||
|
print(f"Files saved in: {output_dir}/")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
113
Printing/Nameplates/generate_zipper_pulls_raised_text_2.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate 3D zipper pull STL files with raised text from a list of names using OpenSCAD.
|
||||||
|
|
||||||
|
This script reads names from a text file and generates individual STL files
|
||||||
|
for each name using the raised text zipper pull OpenSCAD template.
|
||||||
|
|
||||||
|
Version 2: Improved apostrophe handling - preserves apostrophes in both filenames and text rendering.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def generate_stl(name, template_file, output_dir):
|
||||||
|
"""
|
||||||
|
Generate an STL file for a given name using OpenSCAD.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: The name to put on the zipper pull
|
||||||
|
template_file: Path to the OpenSCAD template file
|
||||||
|
output_dir: Directory where STL files will be saved
|
||||||
|
"""
|
||||||
|
# Create a safe filename from the name
|
||||||
|
# Keep apostrophes in filename for accurate representation
|
||||||
|
safe_name = "".join(c for c in name if c.isalnum() or c in (' ', '-', '_', "\'")).strip()
|
||||||
|
safe_name = safe_name.replace(' ', '_')
|
||||||
|
|
||||||
|
output_file = os.path.join(output_dir, f"{safe_name}.stl")
|
||||||
|
|
||||||
|
# Escape the name for OpenSCAD command line
|
||||||
|
# Need to escape both quotes and apostrophes properly
|
||||||
|
escaped_name = name.replace('\\', '\\\\') # Escape backslashes first
|
||||||
|
escaped_name = escaped_name.replace('"', '\\"') # Escape double quotes
|
||||||
|
escaped_name = escaped_name.replace("'", "\\'") # Escape single quotes/apostrophes
|
||||||
|
|
||||||
|
# Build the OpenSCAD command
|
||||||
|
# -D sets a variable, -o specifies output file
|
||||||
|
cmd = [
|
||||||
|
r'C:\Program Files\OpenSCAD\openscad.exe',
|
||||||
|
'-D', f'name="{escaped_name}"',
|
||||||
|
'-o', output_file,
|
||||||
|
template_file
|
||||||
|
]
|
||||||
|
|
||||||
|
print(f"Generating: {safe_name}.stl for '{name}'...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run OpenSCAD
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
print(f" [OK] Successfully created {safe_name}.stl")
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f" [ERROR] Error generating {safe_name}.stl")
|
||||||
|
print(f" {e.stderr}")
|
||||||
|
return False
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Error: OpenSCAD not found. Please install OpenSCAD and ensure it's in your PATH.")
|
||||||
|
print("Download from: https://openscad.org/downloads.html")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Configuration
|
||||||
|
template_file = "zipper_pull_raised_text_template.scad"
|
||||||
|
names_file = "names.txt"
|
||||||
|
output_dir = "zipper-pulls-raised-text-2"
|
||||||
|
|
||||||
|
# Check if template exists
|
||||||
|
if not os.path.exists(template_file):
|
||||||
|
print(f"Error: Template file '{template_file}' not found!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Check if names file exists
|
||||||
|
if not os.path.exists(names_file):
|
||||||
|
print(f"Error: Names file '{names_file}' not found!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Create output directory if it doesn't exist
|
||||||
|
Path(output_dir).mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Read names from file
|
||||||
|
with open(names_file, 'r', encoding='utf-8') as f:
|
||||||
|
names = [line.strip() for line in f if line.strip()]
|
||||||
|
|
||||||
|
if not names:
|
||||||
|
print(f"Error: No names found in '{names_file}'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"Found {len(names)} name(s) to process")
|
||||||
|
print(f"Output directory: {output_dir}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
# Generate STL for each name
|
||||||
|
success_count = 0
|
||||||
|
for name in names:
|
||||||
|
if generate_stl(name, template_file, output_dir):
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
print("-" * 50)
|
||||||
|
print(f"Complete! Generated {success_count}/{len(names)} STL files")
|
||||||
|
print(f"Files saved in: {output_dir}/")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
61
Printing/Nameplates/index.html
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /Printing/Nameplates/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /Printing/Nameplates/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href=".claude/">.claude/</a></li>
|
||||||
|
<li><a href=".github/">.github/</a></li>
|
||||||
|
<li><a href="300.103.pdf">300.103.pdf</a></li>
|
||||||
|
<li><a href="add_filament_change_to_3mf.py">add_filament_change_to_3mf.py</a></li>
|
||||||
|
<li><a href="add_height_modifier_orca.py">add_height_modifier_orca.py</a></li>
|
||||||
|
<li><a href="Check-Mark/">Check-Mark/</a></li>
|
||||||
|
<li><a href="checkmark_template.scad">checkmark_template.scad</a></li>
|
||||||
|
<li><a href="CLAUDE.md">CLAUDE.md</a></li>
|
||||||
|
<li><a href="Fordscript.ttf">Fordscript.ttf</a></li>
|
||||||
|
<li><a href="fred_bennett_facedown.3mf">fred_bennett_facedown.3mf</a></li>
|
||||||
|
<li><a href="generate_nameplates.py">generate_nameplates.py</a></li>
|
||||||
|
<li><a href="generate_zipper_pulls.py">generate_zipper_pulls.py</a></li>
|
||||||
|
<li><a href="generate_zipper_pulls_raised_text.py">generate_zipper_pulls_raised_text.py</a></li>
|
||||||
|
<li><a href="generate_zipper_pulls_raised_text_2.py">generate_zipper_pulls_raised_text_2.py</a></li>
|
||||||
|
<li><a href="nameplate_template.scad">nameplate_template.scad</a></li>
|
||||||
|
<li><a href="names.txt">names.txt</a></li>
|
||||||
|
<li><a href="nul">nul</a></li>
|
||||||
|
<li><a href="output_stl/">output_stl/</a></li>
|
||||||
|
<li><a href="Screenshot%202025-11-07%20082805.png">Screenshot 2025-11-07 082805.png</a></li>
|
||||||
|
<li><a href="test_bennet_facedown.stl">test_bennet_facedown.stl</a></li>
|
||||||
|
<li><a href="test_bennett_facedown.3mf">test_bennett_facedown.3mf</a></li>
|
||||||
|
<li><a href="test_blue_only.3mf">test_blue_only.3mf</a></li>
|
||||||
|
<li><a href="test_blue_only.scad">test_blue_only.scad</a></li>
|
||||||
|
<li><a href="test_christopher.stl">test_christopher.stl</a></li>
|
||||||
|
<li><a href="test_christopher_13pt.stl">test_christopher_13pt.stl</a></li>
|
||||||
|
<li><a href="test_christopher_dynamic.stl">test_christopher_dynamic.stl</a></li>
|
||||||
|
<li><a href="test_christopher_raised.stl">test_christopher_raised.stl</a></li>
|
||||||
|
<li><a href="test_debug.3mf">test_debug.3mf</a></li>
|
||||||
|
<li><a href="test_debug.scad">test_debug.scad</a></li>
|
||||||
|
<li><a href="test_font.scad">test_font.scad</a></li>
|
||||||
|
<li><a href="test_font.stl">test_font.stl</a></li>
|
||||||
|
<li><a href="test_fred_bold.stl">test_fred_bold.stl</a></li>
|
||||||
|
<li><a href="test_fred_debug.stl">test_fred_debug.stl</a></li>
|
||||||
|
<li><a href="test_fred_dynamic.stl">test_fred_dynamic.stl</a></li>
|
||||||
|
<li><a href="test_fred_raised.stl">test_fred_raised.stl</a></li>
|
||||||
|
<li><a href="TEST_Fred_raised_text.stl">TEST_Fred_raised_text.stl</a></li>
|
||||||
|
<li><a href="test_fred_reduced_bold.stl">test_fred_reduced_bold.stl</a></li>
|
||||||
|
<li><a href="test_hole_position.stl">test_hole_position.stl</a></li>
|
||||||
|
<li><a href="test_simple_fred.scad">test_simple_fred.scad</a></li>
|
||||||
|
<li><a href="test_simple_fred.stl">test_simple_fred.stl</a></li>
|
||||||
|
<li><a href="test_zoe.stl">test_zoe.stl</a></li>
|
||||||
|
<li><a href="test_zoe_raised.stl">test_zoe_raised.stl</a></li>
|
||||||
|
<li><a href="zipper-pulls-raised-text/">zipper-pulls-raised-text/</a></li>
|
||||||
|
<li><a href="zipper-pulls-raised-text-2/">zipper-pulls-raised-text-2/</a></li>
|
||||||
|
<li><a href="zipper_pull_raised_text_template.scad">zipper_pull_raised_text_template.scad</a></li>
|
||||||
|
<li><a href="zipper_pull_raised_text_template_REDUCED_BOLD.scad">zipper_pull_raised_text_template_REDUCED_BOLD.scad</a></li>
|
||||||
|
<li><a href="zipper_pull_template.scad">zipper_pull_template.scad</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
57
Printing/Nameplates/nameplate_template.scad
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// Two-Layer Oval Nameplate Template with Engraved Text
|
||||||
|
// Creates a white base oval with a blue top oval and engraved text
|
||||||
|
|
||||||
|
// Parameters - these can be overridden from command line
|
||||||
|
name = "NAME"; // Text to display on nameplate
|
||||||
|
oval_width = 100; // Width of the oval (mm) - max 100mm
|
||||||
|
oval_height = 38; // Height of the oval (mm) - proportional to width
|
||||||
|
base_thickness = 1.5; // Thickness of white base layer (mm)
|
||||||
|
top_thickness = 1; // Thickness of blue top layer (mm)
|
||||||
|
base_offset = 2; // How much larger the white base is (mm)
|
||||||
|
text_size = 15; // Font size for the text
|
||||||
|
text_depth = 1; // How deep the text is engraved (mm) - cuts through blue to show white
|
||||||
|
font_file = "C:/Users/Fred/claude/Fordscript.ttf"; // Path to the custom font file
|
||||||
|
|
||||||
|
// Module to create an oval (ellipse)
|
||||||
|
module oval(width, height, depth) {
|
||||||
|
scale([width/2, height/2, 1])
|
||||||
|
cylinder(h=depth, r=1, $fn=100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// White base layer (larger oval)
|
||||||
|
module base_layer() {
|
||||||
|
color("white")
|
||||||
|
oval(
|
||||||
|
oval_width + base_offset*2,
|
||||||
|
oval_height + base_offset*2,
|
||||||
|
base_thickness
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blue top layer with engraved text
|
||||||
|
module top_layer() {
|
||||||
|
color("RoyalBlue")
|
||||||
|
difference() {
|
||||||
|
// Blue oval
|
||||||
|
translate([0, 0, base_thickness])
|
||||||
|
oval(oval_width, oval_height, top_thickness);
|
||||||
|
|
||||||
|
// Engraved text (cuts into the blue layer)
|
||||||
|
translate([0, 0, base_thickness + top_thickness - text_depth + 0.01])
|
||||||
|
linear_extrude(height=text_depth)
|
||||||
|
text(name,
|
||||||
|
size=text_size,
|
||||||
|
font="Fordscript",
|
||||||
|
halign="center",
|
||||||
|
valign="center");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main nameplate assembly
|
||||||
|
module nameplate() {
|
||||||
|
base_layer();
|
||||||
|
top_layer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the nameplate
|
||||||
|
nameplate();
|
||||||
58
Printing/Nameplates/names.txt
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
Skylar
|
||||||
|
Aaron
|
||||||
|
Kathrynn
|
||||||
|
Alice
|
||||||
|
Jaxxin
|
||||||
|
Sydney
|
||||||
|
Blake
|
||||||
|
Ayden
|
||||||
|
Bentley
|
||||||
|
Landon
|
||||||
|
Silas
|
||||||
|
Aniah
|
||||||
|
Silas
|
||||||
|
Donald
|
||||||
|
Hunter
|
||||||
|
Harper
|
||||||
|
Keith
|
||||||
|
Karleigh
|
||||||
|
Michael
|
||||||
|
Taylor
|
||||||
|
Adalynn
|
||||||
|
Harley
|
||||||
|
Danny
|
||||||
|
Carson
|
||||||
|
Aur'heir
|
||||||
|
Kinleigh
|
||||||
|
Bennett
|
||||||
|
Lucy
|
||||||
|
Grayson
|
||||||
|
Leon
|
||||||
|
Charlotte
|
||||||
|
Landon
|
||||||
|
Aiden
|
||||||
|
Nevaeh
|
||||||
|
Ayla
|
||||||
|
Leah
|
||||||
|
William
|
||||||
|
Lane
|
||||||
|
Liberty
|
||||||
|
Karter
|
||||||
|
Wyatt
|
||||||
|
Clare
|
||||||
|
Bryant
|
||||||
|
Cole
|
||||||
|
Jordan
|
||||||
|
Max
|
||||||
|
Zoey
|
||||||
|
Eli
|
||||||
|
Oliver
|
||||||
|
Jack
|
||||||
|
Tyler
|
||||||
|
Annabella
|
||||||
|
Amaya
|
||||||
|
Shane
|
||||||
|
Nicholas
|
||||||
|
Clay
|
||||||
|
Jayden
|
||||||
|
Alexis
|
||||||
15
Printing/Nameplates/output_stl/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /Printing/Nameplates/output_stl/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /Printing/Nameplates/output_stl/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href="2names%27.3mf">2names'.3mf</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
Printing/Nameplates/test_bennett_facedown.3mf
Normal file
BIN
Printing/Nameplates/test_blue_only.3mf
Normal file
61
Printing/Nameplates/test_blue_only.scad
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// Test - only blue layer from main template
|
||||||
|
name = "Bennett";
|
||||||
|
oval_width = 96;
|
||||||
|
oval_height = 30;
|
||||||
|
blue_thickness = 1;
|
||||||
|
white_thickness = 1.5;
|
||||||
|
base_text_size = 13;
|
||||||
|
|
||||||
|
name_length = len(name);
|
||||||
|
text_size = name_length <= 3 ? 18 :
|
||||||
|
name_length <= 5 ? 13 :
|
||||||
|
name_length <= 8 ? 11 :
|
||||||
|
11;
|
||||||
|
|
||||||
|
border_width = 2;
|
||||||
|
hole_diameter = 4;
|
||||||
|
hole_clearance = 4;
|
||||||
|
|
||||||
|
total_width = oval_width + border_width*2;
|
||||||
|
total_height = oval_height + border_width*2;
|
||||||
|
hole_x = -(total_width/2) + (hole_diameter/2) + hole_clearance;
|
||||||
|
|
||||||
|
module oval(width, height, depth) {
|
||||||
|
scale([width/2, height/2, 1])
|
||||||
|
cylinder(h=depth, r=1, $fn=100);
|
||||||
|
}
|
||||||
|
|
||||||
|
module bold_text() {
|
||||||
|
for (x = [-0.3, 0, 0.3]) {
|
||||||
|
for (y = [-0.3, 0, 0.3]) {
|
||||||
|
translate([x, y, 0])
|
||||||
|
linear_extrude(height=blue_thickness + 0.1)
|
||||||
|
text(name,
|
||||||
|
size=text_size,
|
||||||
|
font="Fordscript",
|
||||||
|
halign="center",
|
||||||
|
valign="center");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blue layer only
|
||||||
|
color("RoyalBlue")
|
||||||
|
difference() {
|
||||||
|
oval(total_width, total_height, blue_thickness);
|
||||||
|
|
||||||
|
// Cut out the border ring channel
|
||||||
|
translate([0, 0, -0.05])
|
||||||
|
difference() {
|
||||||
|
oval(total_width, total_height, blue_thickness + 0.1);
|
||||||
|
oval(oval_width, oval_height, blue_thickness + 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut out text channels
|
||||||
|
translate([0, 0, -0.05])
|
||||||
|
bold_text();
|
||||||
|
|
||||||
|
// Cut out hole
|
||||||
|
translate([hole_x, 0, -0.05])
|
||||||
|
cylinder(h=blue_thickness + 0.1, d=hole_diameter, $fn=50);
|
||||||
|
}
|
||||||
BIN
Printing/Nameplates/test_debug.3mf
Normal file
43
Printing/Nameplates/test_debug.scad
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// Debug - just show the blue layer with text cut out
|
||||||
|
name = "Bennett";
|
||||||
|
oval_width = 96;
|
||||||
|
oval_height = 30;
|
||||||
|
blue_thickness = 1;
|
||||||
|
border_width = 2;
|
||||||
|
|
||||||
|
name_length = len(name);
|
||||||
|
text_size = name_length <= 3 ? 18 :
|
||||||
|
name_length <= 5 ? 13 :
|
||||||
|
name_length <= 8 ? 11 :
|
||||||
|
11;
|
||||||
|
|
||||||
|
total_width = oval_width + border_width*2;
|
||||||
|
total_height = oval_height + border_width*2;
|
||||||
|
|
||||||
|
module oval(width, height, depth) {
|
||||||
|
scale([width/2, height/2, 1])
|
||||||
|
cylinder(h=depth, r=1, $fn=100);
|
||||||
|
}
|
||||||
|
|
||||||
|
module bold_text() {
|
||||||
|
for (x = [-0.3, 0, 0.3]) {
|
||||||
|
for (y = [-0.3, 0, 0.3]) {
|
||||||
|
translate([x, y, 0])
|
||||||
|
linear_extrude(height=blue_thickness + 0.1)
|
||||||
|
text(name,
|
||||||
|
size=text_size,
|
||||||
|
font="Fordscript",
|
||||||
|
halign="center",
|
||||||
|
valign="center");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just the blue with text cut - no border cut
|
||||||
|
color("RoyalBlue")
|
||||||
|
difference() {
|
||||||
|
oval(total_width, total_height, blue_thickness);
|
||||||
|
|
||||||
|
translate([0, 0, -0.05])
|
||||||
|
bold_text();
|
||||||
|
}
|
||||||
3
Printing/Nameplates/test_font.scad
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// Test if Fordscript font renders
|
||||||
|
linear_extrude(height=2)
|
||||||
|
text("Bennett", size=11, font="Fordscript", halign="center", valign="center");
|
||||||
12
Printing/Nameplates/test_simple_fred.scad
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Simple test for Fred text rendering
|
||||||
|
name = "Fred";
|
||||||
|
text_size = 13;
|
||||||
|
|
||||||
|
color("white")
|
||||||
|
translate([0, 0, 1.5])
|
||||||
|
linear_extrude(height=1)
|
||||||
|
text(name,
|
||||||
|
size=text_size,
|
||||||
|
font="Fordscript",
|
||||||
|
halign="center",
|
||||||
|
valign="center");
|
||||||
70
Printing/Nameplates/zipper-pulls-raised-text-2/index.html
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /Printing/Nameplates/zipper-pulls-raised-text-2/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /Printing/Nameplates/zipper-pulls-raised-text-2/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href="Aaron.stl">Aaron.stl</a></li>
|
||||||
|
<li><a href="Adalynn.stl">Adalynn.stl</a></li>
|
||||||
|
<li><a href="Aiden.stl">Aiden.stl</a></li>
|
||||||
|
<li><a href="Alexis.stl">Alexis.stl</a></li>
|
||||||
|
<li><a href="Alice.stl">Alice.stl</a></li>
|
||||||
|
<li><a href="Amaya.stl">Amaya.stl</a></li>
|
||||||
|
<li><a href="Aniah.stl">Aniah.stl</a></li>
|
||||||
|
<li><a href="Annabella.stl">Annabella.stl</a></li>
|
||||||
|
<li><a href="Aur%27heir.stl">Aur'heir.stl</a></li>
|
||||||
|
<li><a href="Ayden.stl">Ayden.stl</a></li>
|
||||||
|
<li><a href="Ayla.stl">Ayla.stl</a></li>
|
||||||
|
<li><a href="Bennett.stl">Bennett.stl</a></li>
|
||||||
|
<li><a href="Bentley.stl">Bentley.stl</a></li>
|
||||||
|
<li><a href="Blake.stl">Blake.stl</a></li>
|
||||||
|
<li><a href="Bryant.stl">Bryant.stl</a></li>
|
||||||
|
<li><a href="Carson.stl">Carson.stl</a></li>
|
||||||
|
<li><a href="Charlotte.stl">Charlotte.stl</a></li>
|
||||||
|
<li><a href="Clare.stl">Clare.stl</a></li>
|
||||||
|
<li><a href="Clay.stl">Clay.stl</a></li>
|
||||||
|
<li><a href="Cole.stl">Cole.stl</a></li>
|
||||||
|
<li><a href="Danny.stl">Danny.stl</a></li>
|
||||||
|
<li><a href="Donald.stl">Donald.stl</a></li>
|
||||||
|
<li><a href="Eli.stl">Eli.stl</a></li>
|
||||||
|
<li><a href="Grayson.stl">Grayson.stl</a></li>
|
||||||
|
<li><a href="Harley.stl">Harley.stl</a></li>
|
||||||
|
<li><a href="Harper.stl">Harper.stl</a></li>
|
||||||
|
<li><a href="Hunter.stl">Hunter.stl</a></li>
|
||||||
|
<li><a href="Jack.stl">Jack.stl</a></li>
|
||||||
|
<li><a href="Jaxxin.stl">Jaxxin.stl</a></li>
|
||||||
|
<li><a href="Jayden.stl">Jayden.stl</a></li>
|
||||||
|
<li><a href="Jordan.stl">Jordan.stl</a></li>
|
||||||
|
<li><a href="Karleigh.stl">Karleigh.stl</a></li>
|
||||||
|
<li><a href="Karter.stl">Karter.stl</a></li>
|
||||||
|
<li><a href="Kathrynn.stl">Kathrynn.stl</a></li>
|
||||||
|
<li><a href="Keith.stl">Keith.stl</a></li>
|
||||||
|
<li><a href="Kinleigh.stl">Kinleigh.stl</a></li>
|
||||||
|
<li><a href="Landon.stl">Landon.stl</a></li>
|
||||||
|
<li><a href="Lane.stl">Lane.stl</a></li>
|
||||||
|
<li><a href="Leah.stl">Leah.stl</a></li>
|
||||||
|
<li><a href="Leon.stl">Leon.stl</a></li>
|
||||||
|
<li><a href="Liberty.stl">Liberty.stl</a></li>
|
||||||
|
<li><a href="Lucy.stl">Lucy.stl</a></li>
|
||||||
|
<li><a href="Max.stl">Max.stl</a></li>
|
||||||
|
<li><a href="Michael.stl">Michael.stl</a></li>
|
||||||
|
<li><a href="Nevaeh.stl">Nevaeh.stl</a></li>
|
||||||
|
<li><a href="Nicholas.stl">Nicholas.stl</a></li>
|
||||||
|
<li><a href="Oliver.stl">Oliver.stl</a></li>
|
||||||
|
<li><a href="Shane.stl">Shane.stl</a></li>
|
||||||
|
<li><a href="Silas.stl">Silas.stl</a></li>
|
||||||
|
<li><a href="Skylar.stl">Skylar.stl</a></li>
|
||||||
|
<li><a href="Sydney.stl">Sydney.stl</a></li>
|
||||||
|
<li><a href="Taylor.stl">Taylor.stl</a></li>
|
||||||
|
<li><a href="Tyler.stl">Tyler.stl</a></li>
|
||||||
|
<li><a href="William.stl">William.stl</a></li>
|
||||||
|
<li><a href="Wyatt.stl">Wyatt.stl</a></li>
|
||||||
|
<li><a href="Zoey.stl">Zoey.stl</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
82
Printing/Nameplates/zipper-pulls-raised-text/index.html
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /Printing/Nameplates/zipper-pulls-raised-text/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /Printing/Nameplates/zipper-pulls-raised-text/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href="111.stl">111.stl</a></li>
|
||||||
|
<li><a href="aaron.3mf">aaron.3mf</a></li>
|
||||||
|
<li><a href="Aaron.stl">Aaron.stl</a></li>
|
||||||
|
<li><a href="Adalynn.stl">Adalynn.stl</a></li>
|
||||||
|
<li><a href="Aiden.stl">Aiden.stl</a></li>
|
||||||
|
<li><a href="Alexis.stl">Alexis.stl</a></li>
|
||||||
|
<li><a href="Alice.stl">Alice.stl</a></li>
|
||||||
|
<li><a href="Amaya.stl">Amaya.stl</a></li>
|
||||||
|
<li><a href="Aniah.stl">Aniah.stl</a></li>
|
||||||
|
<li><a href="Annabella.stl">Annabella.stl</a></li>
|
||||||
|
<li><a href="Aurheir.stl">Aurheir.stl</a></li>
|
||||||
|
<li><a href="Ayden.stl">Ayden.stl</a></li>
|
||||||
|
<li><a href="Ayla.stl">Ayla.stl</a></li>
|
||||||
|
<li><a href="Bennett.stl">Bennett.stl</a></li>
|
||||||
|
<li><a href="Bentley.stl">Bentley.stl</a></li>
|
||||||
|
<li><a href="Blake.stl">Blake.stl</a></li>
|
||||||
|
<li><a href="Bryant.stl">Bryant.stl</a></li>
|
||||||
|
<li><a href="Carson.stl">Carson.stl</a></li>
|
||||||
|
<li><a href="Charlotte.stl">Charlotte.stl</a></li>
|
||||||
|
<li><a href="Clare.stl">Clare.stl</a></li>
|
||||||
|
<li><a href="Clay.stl">Clay.stl</a></li>
|
||||||
|
<li><a href="Cole.stl">Cole.stl</a></li>
|
||||||
|
<li><a href="Daniel.stl">Daniel.stl</a></li>
|
||||||
|
<li><a href="Danny.stl">Danny.stl</a></li>
|
||||||
|
<li><a href="Donald.stl">Donald.stl</a></li>
|
||||||
|
<li><a href="Eli.stl">Eli.stl</a></li>
|
||||||
|
<li><a href="first%20plate.gcode">first plate.gcode</a></li>
|
||||||
|
<li><a href="Ford-Fred%20%282%29.stl">Ford-Fred (2).stl</a></li>
|
||||||
|
<li><a href="Grayson.stl">Grayson.stl</a></li>
|
||||||
|
<li><a href="Harley.stl">Harley.stl</a></li>
|
||||||
|
<li><a href="Harper.stl">Harper.stl</a></li>
|
||||||
|
<li><a href="Hunter.stl">Hunter.stl</a></li>
|
||||||
|
<li><a href="Jack.stl">Jack.stl</a></li>
|
||||||
|
<li><a href="Jaxxin.stl">Jaxxin.stl</a></li>
|
||||||
|
<li><a href="Jayden.stl">Jayden.stl</a></li>
|
||||||
|
<li><a href="Jordan.stl">Jordan.stl</a></li>
|
||||||
|
<li><a href="Karleigh.stl">Karleigh.stl</a></li>
|
||||||
|
<li><a href="Karter.stl">Karter.stl</a></li>
|
||||||
|
<li><a href="Kathrynn.stl">Kathrynn.stl</a></li>
|
||||||
|
<li><a href="Keith.stl">Keith.stl</a></li>
|
||||||
|
<li><a href="Kinleigh.stl">Kinleigh.stl</a></li>
|
||||||
|
<li><a href="Landon.stl">Landon.stl</a></li>
|
||||||
|
<li><a href="Lane.stl">Lane.stl</a></li>
|
||||||
|
<li><a href="Leah.stl">Leah.stl</a></li>
|
||||||
|
<li><a href="Leon.stl">Leon.stl</a></li>
|
||||||
|
<li><a href="Liberty.stl">Liberty.stl</a></li>
|
||||||
|
<li><a href="Lucy.stl">Lucy.stl</a></li>
|
||||||
|
<li><a href="Max.stl">Max.stl</a></li>
|
||||||
|
<li><a href="Michael.stl">Michael.stl</a></li>
|
||||||
|
<li><a href="Nevaeh.stl">Nevaeh.stl</a></li>
|
||||||
|
<li><a href="New%20Names.3mf">New Names.3mf</a></li>
|
||||||
|
<li><a href="Nicholas.stl">Nicholas.stl</a></li>
|
||||||
|
<li><a href="Oliver.stl">Oliver.stl</a></li>
|
||||||
|
<li><a href="pLATE%201.3mf">pLATE 1.3mf</a></li>
|
||||||
|
<li><a href="Shane.stl">Shane.stl</a></li>
|
||||||
|
<li><a href="Silas.stl">Silas.stl</a></li>
|
||||||
|
<li><a href="Skylar.stl">Skylar.stl</a></li>
|
||||||
|
<li><a href="Sydney.stl">Sydney.stl</a></li>
|
||||||
|
<li><a href="Taylor.stl">Taylor.stl</a></li>
|
||||||
|
<li><a href="top4.3mf">top4.3mf</a></li>
|
||||||
|
<li><a href="Tyler.stl">Tyler.stl</a></li>
|
||||||
|
<li><a href="William.stl">William.stl</a></li>
|
||||||
|
<li><a href="Wyatt.stl">Wyatt.stl</a></li>
|
||||||
|
<li><a href="zipper%20pulls.3mf">zipper pulls.3mf</a></li>
|
||||||
|
<li><a href="zipper%20pulls_FINAL.3mf">zipper pulls_FINAL.3mf</a></li>
|
||||||
|
<li><a href="zipper%20pulls_modified.3mf">zipper pulls_modified.3mf</a></li>
|
||||||
|
<li><a href="zipper%20pulls_with_M600.3mf">zipper pulls_with_M600.3mf</a></li>
|
||||||
|
<li><a href="Zoey.stl">Zoey.stl</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
117
Printing/Nameplates/zipper_pull_raised_text_template.scad
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
// Face-Down Oval Zipper Pull Template
|
||||||
|
// Designed for face-down printing: text and border are channels in blue layer (printed first),
|
||||||
|
// then white backing fills the channels and covers the back.
|
||||||
|
// When flipped over, shows white text/border on blue background.
|
||||||
|
|
||||||
|
// Parameters - these can be overridden from command line
|
||||||
|
name = "NAME"; // Text to display on zipper pull
|
||||||
|
oval_width = 96; // Width of blue oval (mm) - total with border will be 100mm (96 + 2*2)
|
||||||
|
oval_height = 30; // Height of blue oval (mm) - total with border will be 34mm (30 + 2*2)
|
||||||
|
blue_thickness = 1; // Thickness of blue layer (mm) - this is the "show" side
|
||||||
|
white_thickness = 1.5; // Thickness of white backing layer (mm)
|
||||||
|
base_text_size = 13; // Base font size for medium names
|
||||||
|
|
||||||
|
// Dynamic font sizing based on name length (mimics Ford oval proportions)
|
||||||
|
// "Fred" (4 chars) at 13mm gives ~20% coverage - our target
|
||||||
|
name_length = len(name);
|
||||||
|
text_size = name_length <= 3 ? 18 : // Short names (Zoe, Sam, Al) - larger (~21% coverage)
|
||||||
|
name_length <= 5 ? 13 : // Medium names (Fred, John, Mary) - standard (~20% coverage)
|
||||||
|
name_length <= 8 ? 11 : // Longer names (Michael, Jessica) - smaller (~25% coverage)
|
||||||
|
11; // Very long names (Christopher) - increased to 11mm (~35% coverage)
|
||||||
|
|
||||||
|
// Border parameters
|
||||||
|
border_width = 2; // Width of the white border around the oval (mm)
|
||||||
|
|
||||||
|
// Hole parameters for zipper pull
|
||||||
|
hole_diameter = 4; // Diameter of the hole (mm)
|
||||||
|
hole_clearance = 4; // Minimum clearance from edge of outer border (mm)
|
||||||
|
|
||||||
|
// Total dimensions
|
||||||
|
total_width = oval_width + border_width*2; // 100mm
|
||||||
|
total_height = oval_height + border_width*2; // 34mm
|
||||||
|
|
||||||
|
// Hole position - on LEFT side when viewed from show side
|
||||||
|
// Since we print face-down, hole appears on RIGHT during printing
|
||||||
|
// When flipped, it will be on the left as expected
|
||||||
|
hole_x = -(total_width/2) + (hole_diameter/2) + hole_clearance;
|
||||||
|
|
||||||
|
// Module to create an oval (ellipse)
|
||||||
|
module oval(width, height, depth) {
|
||||||
|
scale([width/2, height/2, 1])
|
||||||
|
cylinder(h=depth, r=1, $fn=100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module for text with fake bold effect
|
||||||
|
module bold_text() {
|
||||||
|
for (x = [-0.3, 0, 0.3]) {
|
||||||
|
for (y = [-0.3, 0, 0.3]) {
|
||||||
|
translate([x, y, 0])
|
||||||
|
linear_extrude(height=blue_thickness + 0.1)
|
||||||
|
text(name,
|
||||||
|
size=text_size,
|
||||||
|
font="Fordscript",
|
||||||
|
halign="center",
|
||||||
|
valign="center");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blue layer with channels for text and border (prints first, face-down)
|
||||||
|
// The channels will be filled with white when the white layer is printed
|
||||||
|
module blue_layer() {
|
||||||
|
color("RoyalBlue")
|
||||||
|
difference() {
|
||||||
|
// Full blue oval
|
||||||
|
oval(total_width, total_height, blue_thickness);
|
||||||
|
|
||||||
|
// Cut out the border ring channel (will be filled with white)
|
||||||
|
translate([0, 0, -0.05])
|
||||||
|
difference() {
|
||||||
|
oval(total_width, total_height, blue_thickness + 0.1);
|
||||||
|
oval(oval_width, oval_height, blue_thickness + 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut out text channels (will be filled with white)
|
||||||
|
translate([0, 0, -0.05])
|
||||||
|
bold_text();
|
||||||
|
|
||||||
|
// Cut out hole
|
||||||
|
translate([hole_x, 0, -0.05])
|
||||||
|
cylinder(h=blue_thickness + 0.1, d=hole_diameter, $fn=50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// White backing layer (prints second, on top of blue)
|
||||||
|
// Also fills the text and border channels
|
||||||
|
module white_layer() {
|
||||||
|
color("white")
|
||||||
|
difference() {
|
||||||
|
union() {
|
||||||
|
// Main backing layer on top of blue
|
||||||
|
translate([0, 0, blue_thickness])
|
||||||
|
oval(total_width, total_height, white_thickness);
|
||||||
|
|
||||||
|
// Fill the border channel
|
||||||
|
difference() {
|
||||||
|
oval(total_width, total_height, blue_thickness);
|
||||||
|
oval(oval_width, oval_height, blue_thickness + 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the text channels
|
||||||
|
bold_text();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut out hole through entire white layer
|
||||||
|
translate([hole_x, 0, -0.05])
|
||||||
|
cylinder(h=blue_thickness + white_thickness + 0.2, d=hole_diameter, $fn=50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main zipper pull assembly
|
||||||
|
module zipper_pull() {
|
||||||
|
blue_layer();
|
||||||
|
white_layer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the zipper pull
|
||||||
|
zipper_pull();
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// Single-Layer Oval Zipper Pull Template with Raised Text
|
||||||
|
// Creates a blue base oval with raised white text and a hole for zipper attachment
|
||||||
|
|
||||||
|
// Parameters - these can be overridden from command line
|
||||||
|
name = "NAME"; // Text to display on zipper pull
|
||||||
|
oval_width = 96; // Width of blue oval (mm) - total with border will be 100mm (96 + 2*2)
|
||||||
|
oval_height = 30; // Height of blue oval (mm) - total with border will be 34mm (30 + 2*2)
|
||||||
|
base_thickness = 1.5; // Thickness of blue base layer (mm)
|
||||||
|
text_thickness = 1; // Thickness of raised white text (mm)
|
||||||
|
base_text_size = 13; // Base font size for medium names
|
||||||
|
font_file = "C:/Users/Fred/claude/Fordscript.ttf"; // Path to the custom font file
|
||||||
|
|
||||||
|
// Dynamic font sizing based on name length (mimics Ford oval proportions)
|
||||||
|
// "Fred" (4 chars) at 13mm gives ~20% coverage - our target
|
||||||
|
name_length = len(name);
|
||||||
|
text_size = name_length <= 3 ? 18 : // Short names (Zoe, Sam, Al) - larger
|
||||||
|
name_length <= 5 ? 13 : // Medium names (Fred, John, Mary) - standard
|
||||||
|
name_length <= 8 ? 11 : // Longer names (Michael, Jessica) - smaller
|
||||||
|
9; // Very long names (Christopher) - smallest
|
||||||
|
|
||||||
|
// Border parameters
|
||||||
|
border_width = 2; // Width of the white border around the oval (mm)
|
||||||
|
border_thickness = 1; // Thickness of the white border (mm)
|
||||||
|
|
||||||
|
// Hole parameters for zipper pull
|
||||||
|
hole_diameter = 4; // Diameter of the hole (mm)
|
||||||
|
hole_clearance = 3; // Minimum clearance from edge of outer border (mm) - increased to fully clear white border
|
||||||
|
|
||||||
|
// Module to create an oval (ellipse)
|
||||||
|
module oval(width, height, depth) {
|
||||||
|
scale([width/2, height/2, 1])
|
||||||
|
cylinder(h=depth, r=1, $fn=100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blue base layer with hole
|
||||||
|
module base_layer() {
|
||||||
|
color("RoyalBlue")
|
||||||
|
difference() {
|
||||||
|
// Blue base matches the outer size of the white border
|
||||||
|
oval(oval_width + border_width*2, oval_height + border_width*2, base_thickness);
|
||||||
|
|
||||||
|
// Hole for zipper pull - positioned relative to outer edge
|
||||||
|
// Total width = oval_width + border_width*2
|
||||||
|
// Position: left edge + hole_radius + clearance
|
||||||
|
total_width = oval_width + border_width*2;
|
||||||
|
hole_x = -(total_width/2) + (hole_diameter/2) + hole_clearance;
|
||||||
|
translate([hole_x, 0, -0.05])
|
||||||
|
cylinder(h=base_thickness + 0.1, d=hole_diameter, $fn=50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// White border around the oval
|
||||||
|
module white_border() {
|
||||||
|
color("white")
|
||||||
|
translate([0, 0, base_thickness])
|
||||||
|
difference() {
|
||||||
|
// Outer oval (larger)
|
||||||
|
oval(oval_width + border_width*2, oval_height + border_width*2, border_thickness);
|
||||||
|
// Inner oval (cut out the center)
|
||||||
|
translate([0, 0, -0.05])
|
||||||
|
oval(oval_width, oval_height, border_thickness + 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raised white text on top of base - REDUCED bold effect (3 renders instead of 9)
|
||||||
|
module raised_text() {
|
||||||
|
color("white")
|
||||||
|
// Reduced bold effect - only 3 offsets
|
||||||
|
for (x = [-0.4, 0, 0.4]) {
|
||||||
|
translate([x, 0, base_thickness])
|
||||||
|
linear_extrude(height=text_thickness)
|
||||||
|
text(name,
|
||||||
|
size=text_size,
|
||||||
|
font="Fordscript",
|
||||||
|
halign="center",
|
||||||
|
valign="center");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main zipper pull assembly
|
||||||
|
module zipper_pull() {
|
||||||
|
base_layer();
|
||||||
|
white_border();
|
||||||
|
raised_text();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the zipper pull
|
||||||
|
zipper_pull();
|
||||||
90
Printing/Nameplates/zipper_pull_template.scad
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// Two-Layer Oval Zipper Pull Template with Engraved Text
|
||||||
|
// Creates a white base oval with a blue top oval, engraved text, and a hole for zipper attachment
|
||||||
|
|
||||||
|
// Parameters - these can be overridden from command line
|
||||||
|
name = "NAME"; // Text to display on zipper pull
|
||||||
|
oval_width = 96; // Width of blue oval (mm) - white base will be 100mm total (96 + 2*2)
|
||||||
|
oval_height = 30; // Height of blue oval (mm) - white base will be 34mm total (30 + 2*2)
|
||||||
|
base_thickness = 1.5; // Thickness of white base layer (mm)
|
||||||
|
top_thickness = 1; // Thickness of blue top layer (mm)
|
||||||
|
base_offset = 2; // How much larger the white base is (mm)
|
||||||
|
base_text_size = 13; // Base font size for medium names (reduced to prevent interference with hole)
|
||||||
|
text_depth = 1; // How deep the text is engraved (mm) - cuts through blue to show white
|
||||||
|
font_file = "C:/Users/Fred/claude/Fordscript.ttf"; // Path to the custom font file
|
||||||
|
|
||||||
|
// Dynamic font sizing based on name length (mimics Ford oval proportions)
|
||||||
|
// "Fred" (4 chars) at 13mm gives ~20% coverage - our target
|
||||||
|
name_length = len(name);
|
||||||
|
text_size = name_length <= 3 ? 18 : // Short names (Zoe, Sam, Al) - larger
|
||||||
|
name_length <= 5 ? 13 : // Medium names (Fred, John, Mary) - standard
|
||||||
|
name_length <= 8 ? 11 : // Longer names (Michael, Jessica) - smaller
|
||||||
|
9; // Very long names (Christopher) - smallest
|
||||||
|
|
||||||
|
// Hole parameters for zipper pull
|
||||||
|
hole_diameter = 4; // Diameter of the hole (mm)
|
||||||
|
hole_clearance = 1; // Minimum clearance from edge of white base layer (mm)
|
||||||
|
|
||||||
|
// Module to create an oval (ellipse)
|
||||||
|
module oval(width, height, depth) {
|
||||||
|
scale([width/2, height/2, 1])
|
||||||
|
cylinder(h=depth, r=1, $fn=100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// White base layer (larger oval) with hole
|
||||||
|
module base_layer() {
|
||||||
|
color("white")
|
||||||
|
difference() {
|
||||||
|
oval(
|
||||||
|
oval_width + base_offset*2,
|
||||||
|
oval_height + base_offset*2,
|
||||||
|
base_thickness
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hole for zipper pull - positioned relative to white base edge
|
||||||
|
// Calculate from white base width: (oval_width + base_offset*2)
|
||||||
|
white_width = oval_width + base_offset*2;
|
||||||
|
hole_x = -(white_width/2) + (hole_diameter/2) + hole_clearance;
|
||||||
|
translate([hole_x, 0, -0.05])
|
||||||
|
cylinder(h=base_thickness + 0.1, d=hole_diameter, $fn=50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blue top layer with engraved text and hole
|
||||||
|
module top_layer() {
|
||||||
|
color("RoyalBlue")
|
||||||
|
difference() {
|
||||||
|
// Blue oval
|
||||||
|
translate([0, 0, base_thickness])
|
||||||
|
oval(oval_width, oval_height, top_thickness);
|
||||||
|
|
||||||
|
// Engraved text (cuts into the blue layer)
|
||||||
|
// Multiple offset renders create a "fake bold" effect
|
||||||
|
for (x = [-0.3, 0, 0.3]) {
|
||||||
|
for (y = [-0.3, 0, 0.3]) {
|
||||||
|
translate([x, y, base_thickness + top_thickness - text_depth + 0.01])
|
||||||
|
linear_extrude(height=text_depth)
|
||||||
|
text(name,
|
||||||
|
size=text_size,
|
||||||
|
font="Fordscript",
|
||||||
|
halign="center",
|
||||||
|
valign="center");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hole for zipper pull (positioned on left side)
|
||||||
|
// Position: matches white base layer hole position
|
||||||
|
white_width = oval_width + base_offset*2;
|
||||||
|
hole_x = -(white_width/2) + (hole_diameter/2) + hole_clearance;
|
||||||
|
translate([hole_x, 0, base_thickness])
|
||||||
|
cylinder(h=top_thickness + 0.1, d=hole_diameter, $fn=50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main zipper pull assembly
|
||||||
|
module zipper_pull() {
|
||||||
|
base_layer();
|
||||||
|
top_layer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the zipper pull
|
||||||
|
zipper_pull();
|
||||||
20
Printing/index.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /Printing/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /Printing/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href=".claude/">.claude/</a></li>
|
||||||
|
<li><a href=".git">.git</a></li>
|
||||||
|
<li><a href=".gitignore">.gitignore</a></li>
|
||||||
|
<li><a href="CLAUDE.md">CLAUDE.md</a></li>
|
||||||
|
<li><a href="Key%20caps/">Key caps/</a></li>
|
||||||
|
<li><a href="Nameplates/">Nameplates/</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
265
README.md
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
# Fred's Projects - Source of Truth
|
||||||
|
|
||||||
|
This is the master directory for all active projects. It serves as the "source of truth" for Claude Code sessions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start with VS Code Insiders
|
||||||
|
|
||||||
|
**Open the workspace**:
|
||||||
|
```
|
||||||
|
Double-click: fred-workspace.code-workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Or from VS Code: `File → Open Workspace → fred-workspace.code-workspace`
|
||||||
|
|
||||||
|
**Start Claude Code**:
|
||||||
|
```bash
|
||||||
|
cd C:\Users\Fred\projects
|
||||||
|
claude
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tell Claude to load context**:
|
||||||
|
```
|
||||||
|
"Read .claude-context.md to understand my project structure"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Projects Overview
|
||||||
|
|
||||||
|
### 🎯 [claude-workflows](claude-workflows/)
|
||||||
|
**ADHD-friendly productivity tools for Claude Code**
|
||||||
|
- Slash commands (`/push`, `/eod`)
|
||||||
|
- ADHD assistant with sidequest detection
|
||||||
|
- Auto-discovery for cross-project setup
|
||||||
|
|
||||||
|
**Start here when**: Working on Claude Code tooling, productivity features
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏥 [VA-Strategy](VA-Strategy/)
|
||||||
|
**VA disability claims management system**
|
||||||
|
- Goal: 100% VA rating via TDIU
|
||||||
|
- Current: 60% combined (30% highest single)
|
||||||
|
- Tracking, evidence, statements, forms
|
||||||
|
|
||||||
|
**Start here when**: Working on VA claims, medical documentation
|
||||||
|
|
||||||
|
**Quick commands**:
|
||||||
|
```bash
|
||||||
|
cd VA-Strategy
|
||||||
|
git status # Check what's changed
|
||||||
|
cat tracking/master-tracking.md # See current status
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏠 [infrastructure](infrastructure/)
|
||||||
|
**Home network, Home Assistant, smart home**
|
||||||
|
- Home Assistant configuration
|
||||||
|
- ESPHome devices (garage controller, furnace)
|
||||||
|
- Voice assistant system (GPU-accelerated, local)
|
||||||
|
- Network infrastructure (MQTT, DNS-over-TLS)
|
||||||
|
|
||||||
|
**Start here when**: Working on home automation, voice assistant, ESPHome
|
||||||
|
|
||||||
|
**Active subprojects**:
|
||||||
|
- Voice Assistant: Gaming PC + Surface Go
|
||||||
|
- Furnace Control: ESP32 planning phase
|
||||||
|
- Home Assistant: Main config
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚙️ [config](config/)
|
||||||
|
**Shared configuration files**
|
||||||
|
|
||||||
|
Minimal/placeholder for cross-project configs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📚 [claude-code-history](claude-code-history/)
|
||||||
|
**Background: Claude Code session history**
|
||||||
|
|
||||||
|
Session transcripts, state files, stats. Mostly hidden from searches.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `.claude-context.md` | Master context file - tells Claude about all projects |
|
||||||
|
| `fred-workspace.code-workspace` | VS Code multi-root workspace |
|
||||||
|
| `VSCODE-SETUP.md` | Detailed setup guide for VS Code + Claude |
|
||||||
|
| `README.md` | This file - quick reference |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADHD-Friendly Workflow
|
||||||
|
|
||||||
|
### How Sidequest Detection Works
|
||||||
|
|
||||||
|
1. You're working in one project (e.g., VA-Strategy)
|
||||||
|
2. You start exploring something related to another project (e.g., ESP32 for infrastructure)
|
||||||
|
3. Claude detects the context shift
|
||||||
|
4. Claude offers to:
|
||||||
|
- Track it as a side quest
|
||||||
|
- Switch projects formally
|
||||||
|
- Create a new project
|
||||||
|
- Return to original work
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```
|
||||||
|
You: [Working in VA-Strategy on headache log]
|
||||||
|
You: "I wonder if I could automate headache tracking with Home Assistant"
|
||||||
|
|
||||||
|
Claude: 🤔 Side quest detected!
|
||||||
|
|
||||||
|
Current: VA-Strategy (headache log)
|
||||||
|
New idea: HA automation (infrastructure)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
1. Continue exploring (I'll track it)
|
||||||
|
2. Switch to infrastructure project
|
||||||
|
3. Create new "health-automation" project
|
||||||
|
4. Return to headache log
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Workflows
|
||||||
|
|
||||||
|
### Start Working on a Project
|
||||||
|
```bash
|
||||||
|
cd C:\Users\Fred\projects\VA-Strategy
|
||||||
|
claude
|
||||||
|
# Tell Claude what you want to work on
|
||||||
|
```
|
||||||
|
|
||||||
|
### Switch Projects Mid-Session
|
||||||
|
Just tell Claude:
|
||||||
|
```
|
||||||
|
"I want to switch to working on infrastructure now"
|
||||||
|
```
|
||||||
|
|
||||||
|
Claude will track the context switch.
|
||||||
|
|
||||||
|
### Explore a Side Quest
|
||||||
|
```
|
||||||
|
"This is a side quest - I want to explore X for 20 minutes"
|
||||||
|
```
|
||||||
|
|
||||||
|
Claude will set a timer and check in.
|
||||||
|
|
||||||
|
### End of Day
|
||||||
|
```
|
||||||
|
/eod
|
||||||
|
```
|
||||||
|
|
||||||
|
Claude will:
|
||||||
|
- Commit your changes
|
||||||
|
- Show what you accomplished
|
||||||
|
- Prepare for tomorrow
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup Checklist
|
||||||
|
|
||||||
|
- [x] `.claude-context.md` created
|
||||||
|
- [x] Workspace file created
|
||||||
|
- [ ] Open workspace in VS Code Insiders
|
||||||
|
- [ ] Create ADHD assistant state directory:
|
||||||
|
```powershell
|
||||||
|
New-Item -ItemType Directory -Path "$env:USERPROFILE\.claude-assistant" -Force
|
||||||
|
Copy-Item "claude-workflows\.assistant\state.json.template" "$env:USERPROFILE\.claude-assistant\state.json"
|
||||||
|
```
|
||||||
|
- [ ] Start Claude Code session
|
||||||
|
- [ ] Test sidequest detection
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files You Should Know About
|
||||||
|
|
||||||
|
### Global Context
|
||||||
|
- **`.claude-context.md`** - Tells Claude about all your projects
|
||||||
|
- **`fred-workspace.code-workspace`** - Multi-root workspace for VS Code
|
||||||
|
- **`VSCODE-SETUP.md`** - Detailed setup instructions
|
||||||
|
|
||||||
|
### ADHD Assistant
|
||||||
|
- **`claude-workflows/.assistant/personality.md`** - How Claude should behave
|
||||||
|
- **`claude-workflows/.assistant/state.json.template`** - Session state template
|
||||||
|
- **`~/.claude-assistant/state.json`** - Your active state file (to be created)
|
||||||
|
|
||||||
|
### Project-Specific
|
||||||
|
- **`VA-Strategy/CLAUDE.md`** - VA project context
|
||||||
|
- **`VA-Strategy/README.md`** - VA project overview
|
||||||
|
- **`infrastructure/README.md`** - Infrastructure overview
|
||||||
|
- **`claude-workflows/README.md`** - Workflows overview
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
### Adjust ADHD Assistant Behavior
|
||||||
|
|
||||||
|
Edit: `~/.claude-assistant/state.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"preferences": {
|
||||||
|
"intervention_style": "gentle", // gentle | assertive | minimal
|
||||||
|
"stuck_threshold": 3, // How many times before intervention
|
||||||
|
"sidequest_time_limit_minutes": 30, // Check-in time
|
||||||
|
"celebrates_completions": true // Celebrate wins
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add More Projects
|
||||||
|
|
||||||
|
Edit: `fred-workspace.code-workspace`
|
||||||
|
|
||||||
|
Add new folder:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"path": "new-project",
|
||||||
|
"name": "📦 New Project"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
### Claude Code
|
||||||
|
- `/help` - Claude Code help
|
||||||
|
- Ask Claude: "How does sidequest detection work?"
|
||||||
|
|
||||||
|
### Project-Specific
|
||||||
|
- Each project has a README.md
|
||||||
|
- VA-Strategy and infrastructure have CLAUDE.md files
|
||||||
|
|
||||||
|
### Issues
|
||||||
|
Report at: https://github.com/anthropics/claude-code/issues
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Philosophy
|
||||||
|
|
||||||
|
This setup is designed to work **with** ADHD, not against it:
|
||||||
|
|
||||||
|
✓ Side quests are valid exploration
|
||||||
|
✓ Context switching is supported
|
||||||
|
✓ Progress is celebrated
|
||||||
|
✓ No judgment on workflow
|
||||||
|
✓ Gentle nudging, not rigid control
|
||||||
|
|
||||||
|
Claude is here to help you stay aware of what you're working on, not to police your focus.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Ready?** Open `fred-workspace.code-workspace` and start a Claude session!
|
||||||
BIN
Scaled M'Cheyne Bible Reading Plan.xlsx
Normal file
BIN
Screenshot 2025-12-29 220821.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
Screenshot 2025-12-29 224439.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
Screenshot 2025-12-30 212901.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
Screenshot 2025-12-30 213242.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
Screenshot 2025-12-31 001705.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
Screenshot 2025-12-31 001834.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
Screenshot 2025-12-31 001846.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
Screenshot 2025-12-31 133905.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
Screenshot 2026-01-01 201236.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
Screenshot 2026-01-01 203543.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
Screenshot 2026-01-09 102341.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
26
add-acme-provisioner.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Read the config
|
||||||
|
with open('C:/Users/Fred/AppData/Local/Temp/ca.json', 'r') as f:
|
||||||
|
config = json.load(f)
|
||||||
|
|
||||||
|
# Add ACME provisioner
|
||||||
|
acme_provisioner = {
|
||||||
|
"type": "ACME",
|
||||||
|
"name": "acme",
|
||||||
|
"forceCN": True,
|
||||||
|
"claims": {
|
||||||
|
"maxTLSCertDuration": "8760h",
|
||||||
|
"defaultTLSCertDuration": "8760h"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add to provisioners list
|
||||||
|
config['authority']['provisioners'].append(acme_provisioner)
|
||||||
|
|
||||||
|
# Write updated config
|
||||||
|
with open('C:/Users/Fred/AppData/Local/Temp/ca.json', 'w') as f:
|
||||||
|
json.dump(config, f, indent=4)
|
||||||
|
|
||||||
|
print("ACME provisioner added successfully")
|
||||||
29
add-cocktails-to-caddy.sh
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Add Bar Assistant (cocktails.nianticbooks.com) to Caddy
|
||||||
|
|
||||||
|
echo "Adding cocktails.nianticbooks.com to Caddy..."
|
||||||
|
|
||||||
|
sudo tee -a /etc/caddy/Caddyfile > /dev/null << 'EOF'
|
||||||
|
|
||||||
|
# Bar Assistant - Cocktail Recipe Manager
|
||||||
|
cocktails.nianticbooks.com {
|
||||||
|
reverse_proxy http://10.0.10.40:80 {
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
header_up X-Real-IP {remote_host}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Configuration added!"
|
||||||
|
echo ""
|
||||||
|
echo "Reloading Caddy..."
|
||||||
|
sudo systemctl reload caddy
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Testing configuration..."
|
||||||
|
sleep 3
|
||||||
|
curl -I https://cocktails.nianticbooks.com 2>&1 | head -5
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Done! Bar Assistant should now be available at:"
|
||||||
|
echo " https://cocktails.nianticbooks.com"
|
||||||
71
aider-helpers.ps1
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# Aider Helper Functions for Fred's Workflow
|
||||||
|
# Add to PowerShell profile: . C:\Users\Fred\projects\aider-helpers.ps1
|
||||||
|
|
||||||
|
# Quick launch Aider with 7b model (fast, everyday coding)
|
||||||
|
function aider-fast {
|
||||||
|
aider --model ollama/qwen2.5-coder:7b-instruct @args
|
||||||
|
}
|
||||||
|
|
||||||
|
# Launch Aider with 14b model (complex tasks, better reasoning)
|
||||||
|
function aider-smart {
|
||||||
|
aider --model ollama/qwen2.5-coder:14b-instruct-q4_K_M @args
|
||||||
|
}
|
||||||
|
|
||||||
|
# Launch Aider with architect mode (planning, design)
|
||||||
|
function aider-plan {
|
||||||
|
aider --model ollama/qwen2.5-coder:14b-instruct-q4_K_M --architect @args
|
||||||
|
}
|
||||||
|
|
||||||
|
# Quick commit with Aider (use for git commit messages)
|
||||||
|
function aider-commit {
|
||||||
|
aider --model ollama/qwen2.5-coder:7b-instruct --commit
|
||||||
|
}
|
||||||
|
|
||||||
|
# Launch Aider in watch mode (auto-reload on file changes)
|
||||||
|
function aider-watch {
|
||||||
|
aider --model ollama/qwen2.5-coder:7b-instruct --watch-files @args
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show Aider status and current models
|
||||||
|
function aider-status {
|
||||||
|
Write-Host "=== Aider Status ===" -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Available models:" -ForegroundColor Yellow
|
||||||
|
ollama list | Select-String "qwen2.5-coder"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Quick commands:" -ForegroundColor Yellow
|
||||||
|
Write-Host " aider-fast - Fast 7b model (everyday coding)"
|
||||||
|
Write-Host " aider-smart - Powerful 14b model (complex tasks)"
|
||||||
|
Write-Host " aider-plan - Architect mode (planning)"
|
||||||
|
Write-Host " aider-commit - Generate commit messages"
|
||||||
|
Write-Host " aider-watch - Watch mode (auto-reload)"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Example usage:" -ForegroundColor Yellow
|
||||||
|
Write-Host " cd C:\Users\Fred\projects\VA-Strategy"
|
||||||
|
Write-Host " aider-fast"
|
||||||
|
Write-Host " > Add a function to parse headache log entries"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Token usage estimator
|
||||||
|
function aider-estimate {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$model = "7b"
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Host "=== Token Cost Comparison ===" -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Local Ollama (your setup):" -ForegroundColor Green
|
||||||
|
Write-Host " Cost: `$0.00 (free!)"
|
||||||
|
Write-Host " Speed: Fast (8GB RTX 5060)"
|
||||||
|
Write-Host " Privacy: 100% local"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Claude API (Claude Code):" -ForegroundColor Yellow
|
||||||
|
Write-Host " Cost: ~`$3-15 per million tokens"
|
||||||
|
Write-Host " Speed: Depends on network"
|
||||||
|
Write-Host " Limits: Monthly cap"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Recommendation: Use Aider for routine coding, Claude for complex architecture" -ForegroundColor Cyan
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Aider helpers loaded! Type 'aider-status' for quick start." -ForegroundColor Green
|
||||||
20
aider-test.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Simple calculator functions
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
def add(a: int, b: int) -> int:
|
||||||
|
"""Add two integers."""
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
def subtract(a: int, b: int) -> int:
|
||||||
|
"""Subtract two integers."""
|
||||||
|
return a - b
|
||||||
|
|
||||||
|
def multiply(a: int, b: int) -> int:
|
||||||
|
"""Multiply two integers."""
|
||||||
|
return a * b
|
||||||
|
|
||||||
|
def divide(a: Union[int, float], b: Union[int, float]) -> Union[int, float]:
|
||||||
|
"""Divide two numbers. Raises ValueError if divisor is zero."""
|
||||||
|
if b == 0:
|
||||||
|
raise ValueError("Cannot divide by zero.")
|
||||||
|
return a / b
|
||||||
157
alert-investigation-2026-02-03.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
# Prometheus Alert Investigation - Feb 3, 2026
|
||||||
|
|
||||||
|
## 🔍 Investigation Summary
|
||||||
|
|
||||||
|
**Time:** 1:58 PM CST
|
||||||
|
**Investigator:** OpenClaw (Funky)
|
||||||
|
**Scope:** 4 hosts showing as DOWN in Prometheus
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Findings
|
||||||
|
|
||||||
|
### 1. pve-router (10.0.10.2) - Proxmox Host
|
||||||
|
**Status:** ⚠️ Host UP, Monitoring DOWN
|
||||||
|
**Issue:** node_exporter not responding on port 9100
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ ICMP ping: Responding (0.4ms latency)
|
||||||
|
❌ node_exporter (port 9100): Timeout after 2 seconds
|
||||||
|
```
|
||||||
|
|
||||||
|
**Diagnosis:**
|
||||||
|
- Host is online and reachable
|
||||||
|
- node_exporter service likely stopped or not installed
|
||||||
|
- This is your office Proxmox host (i5)
|
||||||
|
|
||||||
|
**Action Required:**
|
||||||
|
```bash
|
||||||
|
ssh root@10.0.10.2
|
||||||
|
systemctl status prometheus-node-exporter
|
||||||
|
systemctl start prometheus-node-exporter
|
||||||
|
systemctl enable prometheus-node-exporter
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. vps-gaming (51.222.12.162) - OVH Gaming VPS
|
||||||
|
**Status:** ⚠️ Host UP, Monitoring DOWN
|
||||||
|
**Issue:** node_exporter not responding on port 9100
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ ICMP ping: Responding (23.8ms latency - normal for OVH Canada)
|
||||||
|
❌ node_exporter (port 9100): Timeout after 2 seconds
|
||||||
|
```
|
||||||
|
|
||||||
|
**Diagnosis:**
|
||||||
|
- Host is online (WireGuard VPN likely working)
|
||||||
|
- node_exporter either not installed or firewall blocking port 9100
|
||||||
|
- Provider: OVH (deadeyeg4ming.vip)
|
||||||
|
|
||||||
|
**Action Required:**
|
||||||
|
```bash
|
||||||
|
ssh root@51.222.12.162
|
||||||
|
# Check if installed
|
||||||
|
systemctl status prometheus-node-exporter
|
||||||
|
|
||||||
|
# If not installed
|
||||||
|
apt update && apt install prometheus-node-exporter -y
|
||||||
|
|
||||||
|
# Check firewall
|
||||||
|
ufw status
|
||||||
|
ufw allow 9100/tcp # If using UFW
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. OpenClaw Gateway (10.0.10.41) - CT 130
|
||||||
|
**Status:** 🔴 Host DOWN / Missing node_exporter
|
||||||
|
**Issue:** Container reachable but node_exporter not installed
|
||||||
|
|
||||||
|
**Note:** This is the OpenClaw container (me!) - node_exporter should be installed for self-monitoring.
|
||||||
|
|
||||||
|
**Action Required:**
|
||||||
|
```bash
|
||||||
|
ssh root@10.0.10.41
|
||||||
|
apt update && apt install prometheus-node-exporter -y
|
||||||
|
systemctl enable --now prometheus-node-exporter
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Available Container (10.0.10.42) - CT 131
|
||||||
|
**Status:** 🟢 Available for use
|
||||||
|
**Issue:** Container available but not yet deployed
|
||||||
|
|
||||||
|
**Note:** This container is available for future use.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Priority Action Items
|
||||||
|
|
||||||
|
### Critical (Affects Real Monitoring)
|
||||||
|
1. **Fix pve-router node_exporter** - This is a production Proxmox host
|
||||||
|
2. **Fix vps-gaming node_exporter** - This is your WireGuard VPN endpoint
|
||||||
|
|
||||||
|
### Low Priority (Game Servers)
|
||||||
|
3. **Decide on minecraft-forge** - Start if needed, or remove from Prometheus config
|
||||||
|
4. **Decide on minecraft-stoneblock** - Start if needed, or remove from Prometheus config
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Quick Fix Commands
|
||||||
|
|
||||||
|
### For pve-router (10.0.10.2)
|
||||||
|
```bash
|
||||||
|
ssh root@10.0.10.2 "apt update && apt install prometheus-node-exporter -y && systemctl enable --now prometheus-node-exporter"
|
||||||
|
```
|
||||||
|
|
||||||
|
### For vps-gaming (51.222.12.162)
|
||||||
|
```bash
|
||||||
|
ssh root@51.222.12.162 "apt update && apt install prometheus-node-exporter -y && systemctl enable --now prometheus-node-exporter && ufw allow 9100/tcp"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clean Up Prometheus Config (Remove Game Servers)
|
||||||
|
If you don't want to monitor stopped game servers:
|
||||||
|
```bash
|
||||||
|
ssh root@10.0.10.25
|
||||||
|
nano /etc/prometheus/prometheus.yml
|
||||||
|
# Comment out or remove the minecraft targets (10.0.10.41, 10.0.10.42)
|
||||||
|
systemctl reload prometheus
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 Expected Outcome
|
||||||
|
|
||||||
|
**After fixes:**
|
||||||
|
- ✅ 2/4 hosts back online (pve-router, vps-gaming)
|
||||||
|
- ✅ Only real infrastructure monitored
|
||||||
|
- ✅ No false positive alerts
|
||||||
|
- ✅ Inbox stays clean
|
||||||
|
|
||||||
|
**Time to fix:** ~5 minutes total
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 Current Alert Status
|
||||||
|
|
||||||
|
These hosts are **NOT firing critical Discord alerts** yet because:
|
||||||
|
- They're in "pending" state (less than 2 minutes down)
|
||||||
|
- Our threshold is **2+ minutes** before triggering
|
||||||
|
|
||||||
|
If you don't fix them, you'll get Discord alerts in:
|
||||||
|
- **~1-2 minutes** from now (they've been down for a while already)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- pve-router and vps-gaming are **real issues** - these should be monitored
|
||||||
|
- Minecraft servers are probably **intentional** - you don't run them 24/7
|
||||||
|
- Consider removing game servers from Prometheus if you don't want to track them
|
||||||
|
|
||||||
|
Let me know if you want me to:
|
||||||
|
1. Fix the node_exporters remotely (if I have SSH access)
|
||||||
|
2. Remove game servers from Prometheus config
|
||||||
|
3. Both!
|
||||||
147
alertmanager-config-updated.yml
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# Alertmanager Configuration for Fred's Homelab - UPDATED
|
||||||
|
# Location: /etc/prometheus/alertmanager.yml
|
||||||
|
# Updated: 2026-02-03 (Reduced alert noise)
|
||||||
|
#
|
||||||
|
# Changes:
|
||||||
|
# - Only CRITICAL alerts trigger Discord notifications
|
||||||
|
# - WARNING alerts are logged but NOT sent to notification channels
|
||||||
|
# - Removed email notifications entirely
|
||||||
|
|
||||||
|
global:
|
||||||
|
resolve_timeout: 5m
|
||||||
|
|
||||||
|
# Root route - all alerts enter here
|
||||||
|
route:
|
||||||
|
# Group alerts by these labels to reduce noise
|
||||||
|
group_by: ['alertname', 'severity', 'instance']
|
||||||
|
|
||||||
|
# Wait 30s before sending first notification (allows grouping)
|
||||||
|
group_wait: 30s
|
||||||
|
|
||||||
|
# Wait 5min before sending additional alerts for same group
|
||||||
|
group_interval: 5m
|
||||||
|
|
||||||
|
# Resend alert every 12 hours if still firing
|
||||||
|
repeat_interval: 12h
|
||||||
|
|
||||||
|
# Default receiver - drops everything (warnings go here)
|
||||||
|
receiver: 'null'
|
||||||
|
|
||||||
|
# Child routes for specific alert types
|
||||||
|
routes:
|
||||||
|
# CRITICAL alerts - send to Discord webhook
|
||||||
|
- matchers:
|
||||||
|
- severity="critical"
|
||||||
|
receiver: 'discord-critical'
|
||||||
|
group_wait: 10s
|
||||||
|
repeat_interval: 1h
|
||||||
|
|
||||||
|
# WARNING alerts - explicitly drop (logged by Prometheus, not sent)
|
||||||
|
- matchers:
|
||||||
|
- severity="warning"
|
||||||
|
receiver: 'null'
|
||||||
|
repeat_interval: 24h
|
||||||
|
|
||||||
|
# Inhibition rules - prevent alert spam
|
||||||
|
inhibit_rules:
|
||||||
|
# If critical alert is firing, suppress warnings for same alert
|
||||||
|
- source_matchers:
|
||||||
|
- severity="critical"
|
||||||
|
target_matchers:
|
||||||
|
- severity="warning"
|
||||||
|
equal: ['alertname', 'instance']
|
||||||
|
|
||||||
|
# If host is down, suppress all other alerts from that host
|
||||||
|
- source_matchers:
|
||||||
|
- alertname="HostDown"
|
||||||
|
target_matchers:
|
||||||
|
- alertname!="HostDown"
|
||||||
|
equal: ['instance']
|
||||||
|
|
||||||
|
# Receivers - define where alerts go
|
||||||
|
receivers:
|
||||||
|
# Null receiver - drops alerts (used for warnings)
|
||||||
|
- name: 'null'
|
||||||
|
|
||||||
|
# Discord webhook for CRITICAL alerts
|
||||||
|
- name: 'discord-critical'
|
||||||
|
webhook_configs:
|
||||||
|
- url: 'https://discord.com/api/webhooks/1462667503301038285/ZVJDuek6VADA-RdI09xJDvqjveOWXgxQnMBcsQzoKwVPnNOACMCL5v-HN55-KVe4IZY0'
|
||||||
|
send_resolved: true
|
||||||
|
http_config:
|
||||||
|
follow_redirects: true
|
||||||
|
max_alerts: 0 # Send all alerts (no limit)
|
||||||
|
|
||||||
|
# ====================================
|
||||||
|
# Deployment Instructions
|
||||||
|
# ====================================
|
||||||
|
#
|
||||||
|
# 1. Backup existing config:
|
||||||
|
# ssh root@10.0.10.25 'cp /etc/prometheus/alertmanager.yml /etc/prometheus/alertmanager.yml.backup'
|
||||||
|
#
|
||||||
|
# 2. Upload this file:
|
||||||
|
# scp alertmanager-config-updated.yml root@10.0.10.25:/etc/prometheus/alertmanager.yml
|
||||||
|
#
|
||||||
|
# 3. Upload updated alert rules:
|
||||||
|
# scp prometheus-alert-rules-updated.yml root@10.0.10.25:/etc/prometheus/rules/homelab-alerts.yml
|
||||||
|
#
|
||||||
|
# 4. Reload Alertmanager:
|
||||||
|
# ssh root@10.0.10.25 'systemctl reload prometheus-alertmanager'
|
||||||
|
#
|
||||||
|
# 5. Reload Prometheus:
|
||||||
|
# ssh root@10.0.10.25 'systemctl reload prometheus'
|
||||||
|
#
|
||||||
|
# 6. Verify configuration:
|
||||||
|
# curl http://10.0.10.25:9093/api/v1/status
|
||||||
|
# curl http://10.0.10.25:9090/api/v1/rules
|
||||||
|
#
|
||||||
|
# 7. Test Discord webhook:
|
||||||
|
# curl -X POST http://10.0.10.25:9093/api/v1/alerts -d '[
|
||||||
|
# {
|
||||||
|
# "labels": {
|
||||||
|
# "alertname": "TestCriticalAlert",
|
||||||
|
# "severity": "critical",
|
||||||
|
# "instance": "test:9100"
|
||||||
|
# },
|
||||||
|
# "annotations": {
|
||||||
|
# "summary": "Test alert - please ignore"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# ]'
|
||||||
|
#
|
||||||
|
# ====================================
|
||||||
|
# Alert Flow Summary
|
||||||
|
# ====================================
|
||||||
|
#
|
||||||
|
# CRITICAL alerts:
|
||||||
|
# Prometheus → Alertmanager → Discord Webhook → Your Discord Server
|
||||||
|
#
|
||||||
|
# WARNING alerts:
|
||||||
|
# Prometheus → Alertmanager → null receiver (logged, not sent)
|
||||||
|
#
|
||||||
|
# You can view WARNING alerts in:
|
||||||
|
# - Prometheus UI: http://10.0.10.25:9090/alerts
|
||||||
|
# - Alertmanager UI: http://10.0.10.25:9093/#/alerts
|
||||||
|
#
|
||||||
|
# ====================================
|
||||||
|
# Expected Behavior After Update
|
||||||
|
# ====================================
|
||||||
|
#
|
||||||
|
# Your Discord will ONLY receive:
|
||||||
|
# ✅ Host completely down (HostDown)
|
||||||
|
# ✅ CPU >95% for 5 minutes (CriticalCPUUsage)
|
||||||
|
# ✅ Memory >95% for 5 minutes (CriticalMemoryUsage)
|
||||||
|
# ✅ Disk <5% free (DiskSpaceCritical)
|
||||||
|
# ✅ Proxmox node down (ProxmoxNodeDown)
|
||||||
|
# ✅ PostgreSQL down (PostgreSQLDown)
|
||||||
|
# ✅ VPS unreachable (VPSDown)
|
||||||
|
# ✅ Prometheus config reload failed
|
||||||
|
#
|
||||||
|
# Your inbox will receive:
|
||||||
|
# 🚫 NOTHING - all email notifications disabled
|
||||||
|
#
|
||||||
|
# Warnings (CPU 80-95%, memory 85-95%, etc.):
|
||||||
|
# 📊 Logged in Prometheus/Alertmanager UI only
|
||||||
|
#
|
||||||
|
# This should dramatically reduce notification noise while still
|
||||||
|
# catching critical issues that need immediate attention.
|
||||||
42
bible-reading-plan/index.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /bible-reading-plan/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /bible-reading-plan/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href=".claude/">.claude/</a></li>
|
||||||
|
<li><a href=".git/">.git/</a></li>
|
||||||
|
<li><a href=".gitignore">.gitignore</a></li>
|
||||||
|
<li><a href="ARCHITECTURE.md">ARCHITECTURE.md</a></li>
|
||||||
|
<li><a href="backend/">backend/</a></li>
|
||||||
|
<li><a href="CHANGES.md">CHANGES.md</a></li>
|
||||||
|
<li><a href="CLAUDE.md">CLAUDE.md</a></li>
|
||||||
|
<li><a href="data/">data/</a></li>
|
||||||
|
<li><a href="deploy-to-vps.sh">deploy-to-vps.sh</a></li>
|
||||||
|
<li><a href="DEPLOY_QUICKSTART.md">DEPLOY_QUICKSTART.md</a></li>
|
||||||
|
<li><a href="DEPLOY_TO_VPS.md">DEPLOY_TO_VPS.md</a></li>
|
||||||
|
<li><a href="deployment/">deployment/</a></li>
|
||||||
|
<li><a href="docker-compose.yml">docker-compose.yml</a></li>
|
||||||
|
<li><a href="Dockerfile.backend">Dockerfile.backend</a></li>
|
||||||
|
<li><a href="Dockerfile.frontend">Dockerfile.frontend</a></li>
|
||||||
|
<li><a href="frontend/">frontend/</a></li>
|
||||||
|
<li><a href="nginx.conf">nginx.conf</a></li>
|
||||||
|
<li><a href="PROJECT_SUMMARY.md">PROJECT_SUMMARY.md</a></li>
|
||||||
|
<li><a href="QUICKSTART.md">QUICKSTART.md</a></li>
|
||||||
|
<li><a href="README.md">README.md</a></li>
|
||||||
|
<li><a href="READY_TO_DEPLOY.md">READY_TO_DEPLOY.md</a></li>
|
||||||
|
<li><a href="Scaled%20M%27Cheyne%20Bible%20Reading%20Plan.xlsx">Scaled M'Cheyne Bible Reading Plan.xlsx</a></li>
|
||||||
|
<li><a href="Screenshot%202025-12-28%20140941.png">Screenshot 2025-12-28 140941.png</a></li>
|
||||||
|
<li><a href="setup.bat">setup.bat</a></li>
|
||||||
|
<li><a href="setup.sh">setup.sh</a></li>
|
||||||
|
<li><a href="SETUP_COMPLETE.md">SETUP_COMPLETE.md</a></li>
|
||||||
|
<li><a href="start-dev.bat">start-dev.bat</a></li>
|
||||||
|
<li><a href="voice-assistants/">voice-assistants/</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
blackmagic/index.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /blackmagic/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /blackmagic/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href=".claude/">.claude/</a></li>
|
||||||
|
<li><a href=".git/">.git/</a></li>
|
||||||
|
<li><a href="atem-config/">atem-config/</a></li>
|
||||||
|
<li><a href="church-video-production-docs/">church-video-production-docs/</a></li>
|
||||||
|
<li><a href="Gemini/">Gemini/</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
caddy-home-assistant-fix-v2.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Home Assistant - Version 2 (without Host header override)
|
||||||
|
bob.nianticbooks.com {
|
||||||
|
reverse_proxy https://10.0.10.24:8123 {
|
||||||
|
header_up X-Forwarded-Host {host}
|
||||||
|
header_up X-Forwarded-Proto {scheme}
|
||||||
|
header_up X-Real-IP {remote_host}
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
|
||||||
|
transport http {
|
||||||
|
tls_insecure_skip_verify
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
caddy-home-assistant-fix.txt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Home Assistant
|
||||||
|
bob.nianticbooks.com {
|
||||||
|
reverse_proxy https://10.0.10.24:8123 {
|
||||||
|
header_up Host {http.reverse_proxy.upstream.hostport}
|
||||||
|
header_up X-Forwarded-Host {host}
|
||||||
|
header_up X-Forwarded-Proto {scheme}
|
||||||
|
header_up X-Real-IP {remote_host}
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
|
||||||
|
transport http {
|
||||||
|
tls_insecure_skip_verify
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
claude-shared/index.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Directory listing for /claude-shared/</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Directory listing for /claude-shared/</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href=".assistant/">.assistant/</a></li>
|
||||||
|
<li><a href="commands/">commands/</a></li>
|
||||||
|
<li><a href="GAMING-RIG-SETUP.md">GAMING-RIG-SETUP.md</a></li>
|
||||||
|
<li><a href="homelab.code-workspace">homelab.code-workspace</a></li>
|
||||||
|
<li><a href="QUICK-START.md">QUICK-START.md</a></li>
|
||||||
|
<li><a href="README.md">README.md</a></li>
|
||||||
|
<li><a href="SETUP-GUIDE.md">SETUP-GUIDE.md</a></li>
|
||||||
|
<li><a href="setup-symlinks.ps1">setup-symlinks.ps1</a></li>
|
||||||
|
<li><a href="setup-symlinks.sh">setup-symlinks.sh</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
32
copy-to-omv.sh
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Copy deployment scripts to OMV share
|
||||||
|
# Run this from your PC/terminal where you have SSH access to Proxmox
|
||||||
|
|
||||||
|
PROXMOX_HOST="10.0.10.3"
|
||||||
|
OPENCLAW_HOST="10.0.10.28"
|
||||||
|
OMV_SCRIPTS="/mnt/omv-backups/scripts"
|
||||||
|
|
||||||
|
echo "📋 Copying deployment scripts to OMV share..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Create scripts directory on Proxmox/OMV
|
||||||
|
echo "Creating scripts directory..."
|
||||||
|
ssh root@${PROXMOX_HOST} "mkdir -p ${OMV_SCRIPTS}"
|
||||||
|
|
||||||
|
# Copy deployment script from OpenClaw to OMV
|
||||||
|
echo "Copying deploy-inline.sh..."
|
||||||
|
ssh root@${OPENCLAW_HOST} "cat /root/.openclaw/workspace/fred-infrastructure/deploy-inline.sh" | \
|
||||||
|
ssh root@${PROXMOX_HOST} "cat > ${OMV_SCRIPTS}/deploy-prometheus-alerts.sh"
|
||||||
|
|
||||||
|
# Make executable
|
||||||
|
ssh root@${PROXMOX_HOST} "chmod +x ${OMV_SCRIPTS}/deploy-prometheus-alerts.sh"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Script copied to OMV share!"
|
||||||
|
echo ""
|
||||||
|
echo "Location: ${PROXMOX_HOST}:${OMV_SCRIPTS}/deploy-prometheus-alerts.sh"
|
||||||
|
echo ""
|
||||||
|
echo "To deploy the alert fix, SSH to Proxmox and run:"
|
||||||
|
echo " ssh root@${PROXMOX_HOST}"
|
||||||
|
echo " bash ${OMV_SCRIPTS}/deploy-prometheus-alerts.sh"
|
||||||
|
echo ""
|
||||||
203
deploy-inline.sh
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Deploy reduced alerts - Run this from your PC/terminal
|
||||||
|
# Usage: bash deploy-inline.sh
|
||||||
|
|
||||||
|
echo "🚀 Deploying alert spam fix to Prometheus..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Execute inside Prometheus container via Proxmox
|
||||||
|
ssh root@10.0.10.3 "pct exec 125 -- bash -s" << 'SCRIPT_END'
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "📦 Backing up configs..."
|
||||||
|
mkdir -p /etc/prometheus/backups
|
||||||
|
cp /etc/prometheus/alertmanager.yml /etc/prometheus/backups/alertmanager.yml.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true
|
||||||
|
cp /etc/prometheus/rules/homelab-alerts.yml /etc/prometheus/backups/homelab-alerts.yml.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true
|
||||||
|
echo "✅ Backups saved"
|
||||||
|
|
||||||
|
echo "📝 Installing new Alertmanager config..."
|
||||||
|
cat > /etc/prometheus/alertmanager.yml << 'EOF'
|
||||||
|
global:
|
||||||
|
resolve_timeout: 5m
|
||||||
|
|
||||||
|
route:
|
||||||
|
group_by: ['alertname', 'severity', 'instance']
|
||||||
|
group_wait: 30s
|
||||||
|
group_interval: 5m
|
||||||
|
repeat_interval: 12h
|
||||||
|
receiver: 'null'
|
||||||
|
routes:
|
||||||
|
- matchers:
|
||||||
|
- severity="critical"
|
||||||
|
receiver: 'discord-critical'
|
||||||
|
group_wait: 10s
|
||||||
|
repeat_interval: 1h
|
||||||
|
- matchers:
|
||||||
|
- severity="warning"
|
||||||
|
receiver: 'null'
|
||||||
|
repeat_interval: 24h
|
||||||
|
|
||||||
|
inhibit_rules:
|
||||||
|
- source_matchers:
|
||||||
|
- severity="critical"
|
||||||
|
target_matchers:
|
||||||
|
- severity="warning"
|
||||||
|
equal: ['alertname', 'instance']
|
||||||
|
- source_matchers:
|
||||||
|
- alertname="HostDown"
|
||||||
|
target_matchers:
|
||||||
|
- alertname!="HostDown"
|
||||||
|
equal: ['instance']
|
||||||
|
|
||||||
|
receivers:
|
||||||
|
- name: 'null'
|
||||||
|
- name: 'discord-critical'
|
||||||
|
webhook_configs:
|
||||||
|
- url: 'https://discord.com/api/webhooks/1462667503301038285/ZVJDuek6VADA-RdI09xJDvqjveOWXgxQnMBcsQzoKwVPnNOACMCL5v-HN55-KVe4IZY0'
|
||||||
|
send_resolved: true
|
||||||
|
http_config:
|
||||||
|
follow_redirects: true
|
||||||
|
max_alerts: 0
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "✅ Alertmanager config updated"
|
||||||
|
|
||||||
|
echo "📝 Installing new alert rules..."
|
||||||
|
cat > /etc/prometheus/rules/homelab-alerts.yml << 'EOF'
|
||||||
|
groups:
|
||||||
|
- name: infrastructure
|
||||||
|
interval: 30s
|
||||||
|
rules:
|
||||||
|
- alert: HostDown
|
||||||
|
expr: up == 0
|
||||||
|
for: 2m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
category: infrastructure
|
||||||
|
annotations:
|
||||||
|
summary: "Host {{ $labels.instance }} is down"
|
||||||
|
description: "{{ $labels.instance }} has been unreachable for 2+ minutes"
|
||||||
|
|
||||||
|
- alert: HighCPUUsage
|
||||||
|
expr: 100 - (avg by(instance, hostname) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
category: performance
|
||||||
|
annotations:
|
||||||
|
summary: "High CPU on {{ $labels.hostname }}"
|
||||||
|
description: "CPU {{ $value | humanize }}%"
|
||||||
|
|
||||||
|
- alert: CriticalCPUUsage
|
||||||
|
expr: 100 - (avg by(instance, hostname) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 95
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
category: performance
|
||||||
|
annotations:
|
||||||
|
summary: "CRITICAL CPU on {{ $labels.hostname }}"
|
||||||
|
description: "CPU {{ $value | humanize }}%"
|
||||||
|
|
||||||
|
- alert: HighMemoryUsage
|
||||||
|
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 85
|
||||||
|
for: 10m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
category: performance
|
||||||
|
annotations:
|
||||||
|
summary: "High memory on {{ $labels.hostname }}"
|
||||||
|
description: "Memory {{ $value | humanize }}%"
|
||||||
|
|
||||||
|
- alert: CriticalMemoryUsage
|
||||||
|
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 95
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
category: performance
|
||||||
|
annotations:
|
||||||
|
summary: "CRITICAL memory on {{ $labels.hostname }}"
|
||||||
|
description: "Memory {{ $value | humanize }}%"
|
||||||
|
|
||||||
|
- name: storage
|
||||||
|
interval: 1m
|
||||||
|
rules:
|
||||||
|
- alert: DiskSpaceLow
|
||||||
|
expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.lxcfs|squashfs|overlay"} / node_filesystem_size_bytes) * 100 < 15
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
category: storage
|
||||||
|
annotations:
|
||||||
|
summary: "Low disk on {{ $labels.hostname }}"
|
||||||
|
description: "{{ $labels.mountpoint }} has {{ $value | humanize }}% free"
|
||||||
|
|
||||||
|
- alert: DiskSpaceCritical
|
||||||
|
expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.lxcfs|squashfs|overlay"} / node_filesystem_size_bytes) * 100 < 5
|
||||||
|
for: 2m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
category: storage
|
||||||
|
annotations:
|
||||||
|
summary: "CRITICAL disk on {{ $labels.hostname }}"
|
||||||
|
description: "{{ $labels.mountpoint }} only {{ $value | humanize }}% free!"
|
||||||
|
|
||||||
|
- name: services
|
||||||
|
interval: 1m
|
||||||
|
rules:
|
||||||
|
- alert: PostgreSQLDown
|
||||||
|
expr: up{app="postgres"} == 0
|
||||||
|
for: 2m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
category: database
|
||||||
|
annotations:
|
||||||
|
summary: "PostgreSQL is down"
|
||||||
|
description: "PostgreSQL down for 2+ minutes"
|
||||||
|
|
||||||
|
- alert: VPSDown
|
||||||
|
expr: up{role="vps"} == 0
|
||||||
|
for: 2m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
category: infrastructure
|
||||||
|
annotations:
|
||||||
|
summary: "VPS unreachable"
|
||||||
|
description: "VPS down for 2+ minutes"
|
||||||
|
|
||||||
|
- alert: ProxmoxNodeDown
|
||||||
|
expr: up{role="proxmox-host"} == 0
|
||||||
|
for: 2m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
category: infrastructure
|
||||||
|
annotations:
|
||||||
|
summary: "Proxmox node down"
|
||||||
|
description: "Proxmox host unreachable for 2+ minutes"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "✅ Alert rules updated"
|
||||||
|
|
||||||
|
echo "🔄 Reloading services..."
|
||||||
|
systemctl reload prometheus
|
||||||
|
systemctl reload prometheus-alertmanager
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ DEPLOYMENT COMPLETE!"
|
||||||
|
echo ""
|
||||||
|
echo "📊 Changes applied:"
|
||||||
|
echo " • CPU warning: 80%+ over 5min (logged only)"
|
||||||
|
echo " • CPU critical: 95%+ over 5min (Discord)"
|
||||||
|
echo " • Only CRITICAL alerts → Discord"
|
||||||
|
echo " • WARNING alerts → Logged, not sent"
|
||||||
|
echo " • Email notifications → DISABLED"
|
||||||
|
echo ""
|
||||||
|
echo "Your inbox spam should STOP immediately!"
|
||||||
|
echo ""
|
||||||
|
echo "🧪 Test Discord webhook:"
|
||||||
|
echo " curl -X POST http://localhost:9093/api/v1/alerts -d '[{\"labels\":{\"alertname\":\"Test\",\"severity\":\"critical\"},\"annotations\":{\"summary\":\"Test alert\"}}]'"
|
||||||
|
|
||||||
|
SCRIPT_END
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🎉 Done! Check your Discord for the test alert."
|
||||||
82
deploy-reduced-alerts.sh
Executable file
@@ -0,0 +1,82 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Deploy Reduced Alert Configuration
|
||||||
|
# Updates Prometheus alert rules and Alertmanager config to reduce notification noise
|
||||||
|
# Only CRITICAL alerts trigger Discord notifications
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
PROMETHEUS_HOST="10.0.10.25"
|
||||||
|
PROMETHEUS_USER="root"
|
||||||
|
|
||||||
|
echo "🚀 Deploying reduced alert configuration to Prometheus..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if files exist
|
||||||
|
if [ ! -f "prometheus-alert-rules-updated.yml" ]; then
|
||||||
|
echo "❌ Error: prometheus-alert-rules-updated.yml not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "alertmanager-config-updated.yml" ]; then
|
||||||
|
echo "❌ Error: alertmanager-config-updated.yml not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Backup existing configs
|
||||||
|
echo "📦 Backing up existing configurations..."
|
||||||
|
ssh ${PROMETHEUS_USER}@${PROMETHEUS_HOST} 'mkdir -p /etc/prometheus/backups'
|
||||||
|
ssh ${PROMETHEUS_USER}@${PROMETHEUS_HOST} "cp /etc/prometheus/alertmanager.yml /etc/prometheus/backups/alertmanager.yml.$(date +%Y%m%d-%H%M%S)" 2>/dev/null || true
|
||||||
|
ssh ${PROMETHEUS_USER}@${PROMETHEUS_HOST} "cp /etc/prometheus/rules/homelab-alerts.yml /etc/prometheus/backups/homelab-alerts.yml.$(date +%Y%m%d-%H%M%S)" 2>/dev/null || true
|
||||||
|
echo "✅ Backups created in /etc/prometheus/backups/"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Upload new configs
|
||||||
|
echo "📤 Uploading new configurations..."
|
||||||
|
scp alertmanager-config-updated.yml ${PROMETHEUS_USER}@${PROMETHEUS_HOST}:/etc/prometheus/alertmanager.yml
|
||||||
|
scp prometheus-alert-rules-updated.yml ${PROMETHEUS_USER}@${PROMETHEUS_HOST}:/etc/prometheus/rules/homelab-alerts.yml
|
||||||
|
echo "✅ Files uploaded"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Reload services
|
||||||
|
echo "🔄 Reloading Prometheus and Alertmanager..."
|
||||||
|
ssh ${PROMETHEUS_USER}@${PROMETHEUS_HOST} 'systemctl reload prometheus'
|
||||||
|
ssh ${PROMETHEUS_USER}@${PROMETHEUS_HOST} 'systemctl reload prometheus-alertmanager'
|
||||||
|
echo "✅ Services reloaded"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Verify configuration
|
||||||
|
echo "🔍 Verifying configuration..."
|
||||||
|
echo ""
|
||||||
|
echo "Prometheus status:"
|
||||||
|
ssh ${PROMETHEUS_USER}@${PROMETHEUS_HOST} 'systemctl status prometheus --no-pager -l | head -10'
|
||||||
|
echo ""
|
||||||
|
echo "Alertmanager status:"
|
||||||
|
ssh ${PROMETHEUS_USER}@${PROMETHEUS_HOST} 'systemctl status prometheus-alertmanager --no-pager -l | head -10'
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test API endpoints
|
||||||
|
echo "Testing API endpoints..."
|
||||||
|
echo ""
|
||||||
|
echo "Prometheus rules API:"
|
||||||
|
curl -s http://${PROMETHEUS_HOST}:9090/api/v1/rules | head -200
|
||||||
|
echo ""
|
||||||
|
echo "Alertmanager status API:"
|
||||||
|
curl -s http://${PROMETHEUS_HOST}:9093/api/v1/status | head -100
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "✅ Deployment complete!"
|
||||||
|
echo ""
|
||||||
|
echo "📊 Summary of changes:"
|
||||||
|
echo " • CPU alert threshold: 80%+ over 5 minutes (warning)"
|
||||||
|
echo " • CPU critical threshold: 95%+ over 5 minutes (notification)"
|
||||||
|
echo " • Only CRITICAL alerts sent to Discord"
|
||||||
|
echo " • WARNING alerts logged but NOT sent"
|
||||||
|
echo " • Email notifications completely disabled"
|
||||||
|
echo ""
|
||||||
|
echo "🔗 Check alert status:"
|
||||||
|
echo " Prometheus: http://${PROMETHEUS_HOST}:9090/alerts"
|
||||||
|
echo " Alertmanager: http://${PROMETHEUS_HOST}:9093/#/alerts"
|
||||||
|
echo ""
|
||||||
|
echo "🧪 Test critical alert (sends to Discord):"
|
||||||
|
echo " curl -X POST http://${PROMETHEUS_HOST}:9093/api/v1/alerts -d '[{\"labels\":{\"alertname\":\"TestCriticalAlert\",\"severity\":\"critical\",\"instance\":\"test:9100\"},\"annotations\":{\"summary\":\"Test - please ignore\"}}]'"
|
||||||
|
echo ""
|
||||||
134
deploy-uptime-kuma.sh
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Deploy Uptime Kuma on main-pve
|
||||||
|
# LXC Container 128 at 10.0.10.26
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Uptime Kuma Deployment Script ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
VMID=128
|
||||||
|
HOSTNAME="uptime-kuma"
|
||||||
|
IP="10.0.10.26"
|
||||||
|
GATEWAY="10.0.10.1"
|
||||||
|
CORES=2
|
||||||
|
MEMORY=2048
|
||||||
|
SWAP=512
|
||||||
|
DISK="8"
|
||||||
|
TEMPLATE="local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst"
|
||||||
|
|
||||||
|
echo "Configuration:"
|
||||||
|
echo " VMID: $VMID"
|
||||||
|
echo " Hostname: $HOSTNAME"
|
||||||
|
echo " IP: $IP/24"
|
||||||
|
echo " Resources: ${CORES} cores, ${MEMORY}MB RAM, ${DISK}GB disk"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if container already exists
|
||||||
|
if ssh root@10.0.10.3 "pct status $VMID 2>/dev/null"; then
|
||||||
|
echo "⚠️ Container $VMID already exists!"
|
||||||
|
echo ""
|
||||||
|
read -p "Delete and recreate? (y/N): " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Stopping and removing container $VMID..."
|
||||||
|
ssh root@10.0.10.3 "pct stop $VMID 2>/dev/null || true"
|
||||||
|
sleep 2
|
||||||
|
ssh root@10.0.10.3 "pct destroy $VMID"
|
||||||
|
echo "✅ Container removed"
|
||||||
|
else
|
||||||
|
echo "❌ Aborted"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Creating LXC container..."
|
||||||
|
ssh root@10.0.10.3 << EOF
|
||||||
|
pct create $VMID $TEMPLATE \
|
||||||
|
--hostname $HOSTNAME \
|
||||||
|
--cores $CORES \
|
||||||
|
--memory $MEMORY \
|
||||||
|
--swap $SWAP \
|
||||||
|
--net0 name=eth0,bridge=vmbr0,ip=$IP/24,gw=$GATEWAY \
|
||||||
|
--storage local-lvm \
|
||||||
|
--rootfs 8 \
|
||||||
|
--unprivileged 1 \
|
||||||
|
--features nesting=1
|
||||||
|
|
||||||
|
echo "✅ Container created"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Starting container..."
|
||||||
|
pct start $VMID
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
echo "✅ Container started"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Installing Docker..."
|
||||||
|
pct exec $VMID -- bash -c '
|
||||||
|
apt update
|
||||||
|
apt install -y docker.io curl
|
||||||
|
systemctl enable docker
|
||||||
|
systemctl start docker
|
||||||
|
'
|
||||||
|
|
||||||
|
echo "✅ Docker installed"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Deploying Uptime Kuma..."
|
||||||
|
pct exec $VMID -- bash -c "
|
||||||
|
docker run -d \
|
||||||
|
--name uptime-kuma \
|
||||||
|
--restart=always \
|
||||||
|
-p 3001:3001 \
|
||||||
|
-v uptime-kuma:/app/data \
|
||||||
|
louislam/uptime-kuma:2
|
||||||
|
|
||||||
|
echo 'Waiting for Uptime Kuma to start...'
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# Verify container is running
|
||||||
|
if docker ps | grep -q uptime-kuma; then
|
||||||
|
echo '✅ Uptime Kuma container running'
|
||||||
|
else
|
||||||
|
echo '❌ Uptime Kuma failed to start'
|
||||||
|
docker logs uptime-kuma
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Testing Uptime Kuma endpoint..."
|
||||||
|
sleep 5
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Test from host
|
||||||
|
if curl -s -o /dev/null -w "%{http_code}" http://$IP:3001 | grep -q 200; then
|
||||||
|
echo "✅ Uptime Kuma is responding!"
|
||||||
|
else
|
||||||
|
echo "⚠️ Uptime Kuma not responding yet (may need more time to start)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Deployment Complete! ==="
|
||||||
|
echo ""
|
||||||
|
echo "🎉 Uptime Kuma deployed successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "Access Uptime Kuma:"
|
||||||
|
echo " URL: http://$IP:3001"
|
||||||
|
echo " Initial setup required on first visit"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Go to http://$IP:3001"
|
||||||
|
echo "2. Create admin account"
|
||||||
|
echo "3. Add monitors for your services"
|
||||||
|
echo "4. Create status page"
|
||||||
|
echo ""
|
||||||
|
echo "Container management:"
|
||||||
|
echo " Start: ssh root@10.0.10.3 'pct start $VMID'"
|
||||||
|
echo " Stop: ssh root@10.0.10.3 'pct stop $VMID'"
|
||||||
|
echo " Shell: ssh root@10.0.10.3 'pct enter $VMID'"
|
||||||
|
echo " Logs: ssh root@10.0.10.3 'pct exec $VMID -- docker logs uptime-kuma -f'"
|
||||||
|
echo ""
|
||||||
36
fred-workspace 2.code-workspace
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"name": "🎯 Claude Workflows",
|
||||||
|
"path": "claude-workflows"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "🏥 VA Strategy",
|
||||||
|
"path": "VA-Strategy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "🏠 Infrastructure",
|
||||||
|
"path": "infrastructure"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "⚙️ Config",
|
||||||
|
"path": "config"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"files.exclude": {
|
||||||
|
"**/node_modules": true,
|
||||||
|
"**/.git": false,
|
||||||
|
"**/claude-code-history": true
|
||||||
|
},
|
||||||
|
"search.exclude": {
|
||||||
|
"**/node_modules": true,
|
||||||
|
"**/claude-code-history": true
|
||||||
|
},
|
||||||
|
"files.watcherExclude": {
|
||||||
|
"**/node_modules/**": true,
|
||||||
|
"**/claude-code-history/**": true
|
||||||
|
},
|
||||||
|
"powershell.cwd": "🏠 Infrastructure"
|
||||||
|
}
|
||||||
|
}
|
||||||
36
fred-workspace.code-workspace
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "claude-workflows",
|
||||||
|
"name": "🎯 Claude Workflows"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "VA-Strategy",
|
||||||
|
"name": "🏥 VA Strategy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "infrastructure",
|
||||||
|
"name": "🏠 Infrastructure"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "config",
|
||||||
|
"name": "⚙️ Config"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"files.exclude": {
|
||||||
|
"**/node_modules": true,
|
||||||
|
"**/.git": false,
|
||||||
|
"**/claude-code-history": true
|
||||||
|
},
|
||||||
|
"search.exclude": {
|
||||||
|
"**/node_modules": true,
|
||||||
|
"**/claude-code-history": true
|
||||||
|
},
|
||||||
|
"files.watcherExclude": {
|
||||||
|
"**/node_modules/**": true,
|
||||||
|
"**/claude-code-history/**": true
|
||||||
|
},
|
||||||
|
"powershell.cwd": "🏠 Infrastructure"
|
||||||
|
}
|
||||||
|
}
|
||||||