-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtemplate_manager.py
More file actions
317 lines (269 loc) · 10.3 KB
/
template_manager.py
File metadata and controls
317 lines (269 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
"""
KiRender Template Manager
Unified template system for SpinRender, Static Images, and Pinout tabs.
All templates in ONE folder, filtered by 'template_type' field.
Template Schema v1.0:
- version: Schema version for compatibility
- locked: True for built-in templates (read-only)
- name: Display name
- description: Template description
- template_type: "spinrender", "static", or "pinout"
- created: ISO timestamp
- modified: ISO timestamp
- cli_params: All kicad-cli pcb render parameters (see cli_builder.py for schema)
"""
import os
import json
from datetime import datetime
from typing import Dict, List, Optional, Any
# Import CLI schema from the single source of truth
from .cli_builder import CLI_SCHEMA, get_default_params
# Template schema version
SCHEMA_VERSION = "1.0"
# Base templates directory - ONE folder for all
TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), 'templates')
BUILTIN_DIR = os.path.join(TEMPLATES_DIR, 'builtin')
USER_DIR = os.path.join(TEMPLATES_DIR, 'user')
# Template types
TEMPLATE_TYPES = ['spinrender', 'static', 'pinout']
def get_empty_template(template_type: str = "static") -> Dict[str, Any]:
"""Return an empty template with default CLI parameters."""
return {
"version": SCHEMA_VERSION,
"locked": False,
"name": "",
"description": "",
"template_type": template_type,
"created": datetime.now().isoformat(),
"modified": datetime.now().isoformat(),
"cli_params": get_default_params()
}
def load_template(filepath: str) -> Optional[Dict[str, Any]]:
"""Load a single template from file."""
try:
with open(filepath, 'r', encoding='utf-8') as f:
template = json.load(f)
# Ensure required fields
template.setdefault("version", "1.0")
template.setdefault("locked", False)
template.setdefault("template_type", "static")
template.setdefault("cli_params", {})
# Fill missing CLI params with defaults from cli_builder
defaults = get_default_params()
for key, default_val in defaults.items():
template["cli_params"].setdefault(key, default_val)
return template
except Exception as e:
print(f"Error loading template {filepath}: {e}")
return None
def load_all_templates(template_type: str = None) -> Dict[str, Dict[str, Any]]:
"""Load all templates, optionally filtered by type."""
templates = {}
for subdir, is_builtin in [(BUILTIN_DIR, True), (USER_DIR, False)]:
if not os.path.exists(subdir):
os.makedirs(subdir, exist_ok=True)
continue
for filename in os.listdir(subdir):
if filename.endswith('.json'):
filepath = os.path.join(subdir, filename)
template = load_template(filepath)
if template:
# Filter by type if specified
ttype = template.get("template_type") or template.get("type")
if template_type and ttype != template_type:
continue
name = template.get("name", filename[:-5])
template["_filepath"] = filepath
template["_is_builtin"] = is_builtin
templates[name] = template
return templates
def save_template(template: Dict[str, Any], name: str = None) -> str:
"""Save a template to user directory. Returns filepath."""
if name:
template["name"] = name
# Update metadata
template["version"] = SCHEMA_VERSION
template["modified"] = datetime.now().isoformat()
template["locked"] = False
if "created" not in template:
template["created"] = template["modified"]
# Generate safe filename
safe_name = "".join(c if c.isalnum() or c in "._- " else "_" for c in template["name"])
safe_name = safe_name.strip().replace(" ", "_").lower()
filename = f"{safe_name}.json"
# Save to user directory
os.makedirs(USER_DIR, exist_ok=True)
filepath = os.path.join(USER_DIR, filename)
# Remove internal fields before saving
save_data = {k: v for k, v in template.items() if not k.startswith("_")}
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(save_data, f, indent=2)
return filepath
def delete_template(template: Dict[str, Any]) -> bool:
"""Delete a user template. Returns True if successful."""
if template.get("locked") or template.get("_is_builtin"):
return False
filepath = template.get("_filepath")
if filepath and os.path.exists(filepath):
os.remove(filepath)
return True
return False
def duplicate_template(template: Dict[str, Any], new_name: str) -> str:
"""Duplicate a template with a new name. Returns filepath."""
new_template = {k: v for k, v in template.items() if not k.startswith("_")}
new_template["name"] = new_name
new_template["locked"] = False
new_template["created"] = datetime.now().isoformat()
return save_template(new_template)
# ============== CLI Generator (delegates to cli_builder) ==============
def build_cli_args(cli_params: Dict[str, Any], kicad_cli: str, pcb_path: str, output_path: str) -> List[str]:
"""Build complete CLI argument list from template parameters.
NOTE: This delegates to cli_builder.build_cli_command() which is the
single source of truth for CLI construction.
"""
from .cli_builder import build_cli_command
return build_cli_command(cli_params, kicad_cli, pcb_path, output_path)
def get_cli_string(cli_params: Dict[str, Any], kicad_cli: str = "kicad-cli",
pcb_path: str = "board.kicad_pcb", output_path: str = "output.png") -> str:
"""Get CLI command as string for display/logging."""
from .cli_builder import get_cli_string as _get_cli_string
return _get_cli_string(cli_params, kicad_cli, pcb_path, output_path)
# ============== Built-in Template Initialization ==============
BUILTIN_TEMPLATES = {
# SpinRender templates
"Studio Pro": {
"template_type": "spinrender",
"description": "Professional studio look with high quality rendering",
"cli_params": {
"width": 1920, "height": 1080,
"quality": "high",
"perspective": False,
"zoom": 1.0,
}
},
"Technical Clean": {
"template_type": "spinrender",
"description": "Clean technical render for documentation",
"cli_params": {
"width": 1600, "height": 900,
"quality": "high",
"perspective": False,
}
},
"Cinematic": {
"template_type": "spinrender",
"description": "Dramatic angled view for presentations",
"cli_params": {
"width": 1920, "height": 1080,
"quality": "high",
"perspective": True,
"zoom": 1.2,
"rotate": "-30,0,30",
}
},
"Isometric View": {
"template_type": "spinrender",
"description": "Classic isometric angle for documentation",
"cli_params": {
"width": 1600, "height": 1600,
"quality": "high",
"perspective": False,
"rotate": "-45,0,45",
}
},
# Static Image templates
"Product Shot": {
"template_type": "static",
"description": "Clean product image with white background",
"cli_params": {
"width": 2400, "height": 1600,
"side": "top",
"background": "opaque",
"quality": "high",
}
},
"Technical Top": {
"template_type": "static",
"description": "Top-down technical view",
"cli_params": {
"width": 2400, "height": 1600,
"side": "top",
"background": "transparent",
"quality": "high",
}
},
"Technical Bottom": {
"template_type": "static",
"description": "Bottom-side view for assembly documentation",
"cli_params": {
"width": 2400, "height": 1600,
"side": "bottom",
"background": "transparent",
"quality": "high",
}
},
"Showcase": {
"template_type": "static",
"description": "Angled showcase view",
"cli_params": {
"width": 1920, "height": 1080,
"side": "front",
"background": "opaque",
"quality": "high",
"perspective": True,
"rotate": "-20,0,15",
"zoom": 1.1,
}
},
# Pinout templates
"Pinout Clean": {
"template_type": "pinout",
"description": "Clean pinout diagram with transparent background",
"cli_params": {
"width": 2400, "height": 1600,
"side": "top",
"background": "transparent",
"quality": "basic",
}
},
"Pinout High Quality": {
"template_type": "pinout",
"description": "High quality raytraced pinout",
"cli_params": {
"width": 2400, "height": 1600,
"side": "top",
"background": "transparent",
"quality": "high",
}
},
"Pinout Bottom Side": {
"template_type": "pinout",
"description": "Bottom-side pinout diagram",
"cli_params": {
"width": 2400, "height": 1600,
"side": "bottom",
"background": "transparent",
"quality": "high",
}
},
}
def create_builtin_templates():
"""Create built-in templates if they don't exist."""
os.makedirs(BUILTIN_DIR, exist_ok=True)
os.makedirs(USER_DIR, exist_ok=True)
for name, data in BUILTIN_TEMPLATES.items():
# Create full template structure
template = get_empty_template(data.get("template_type", "static"))
template["name"] = name
template["locked"] = True
template["description"] = data.get("description", "")
template["cli_params"].update(data.get("cli_params", {}))
# Save
safe_name = name.lower().replace(" ", "_")
filepath = os.path.join(BUILTIN_DIR, f"{safe_name}.json")
# Only create if doesn't exist
if not os.path.exists(filepath):
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(template, f, indent=2)
# Initialize built-in templates on module load
create_builtin_templates()