Repository navigation
Expand file tree
/
Copy pathghpython_script_browser.py
More file actions
406 lines (325 loc) · 12.5 KB
/
Copy pathghpython_script_browser.py
File metadata and controls
406 lines (325 loc) · 12.5 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
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
"""
Script Store Browser
Browse and load scripts from Script Store
@input: category, script_id, load
@output: scripts, script_ids, versions, load_result
Inputs:
- category (str): Filter by category (Value List "category")
- script_id (str): Script to view/load (Value List "Scripts")
- load (bool): Load the selected script into canvas (Button)
Outputs:
- scripts (str): List of scripts or script details
- script_ids (list): List of script IDs for reference
- versions (str): Version history (if script_id provided)
- load_result (str): Result of load operation
Value List Auto-Population (by NickName):
- "category" → massing, unit_study, facade, analysis, optimization, documentation, other
- "Scripts" → Script names with IDs (populated after category selection)
Version: 1.1.0
Date: 2025-12-26
"""
import json
import urllib.request
import urllib.error
import tempfile
import os
import Grasshopper as gh
# ============================================
# CONFIGURATION - config.py에서 불러오기
# ============================================
try:
from config import SUPABASE_URL, SUPABASE_KEY
except ImportError:
print("WARNING: config.py not found. Please copy config.example.py to config.py")
SUPABASE_URL = ""
SUPABASE_KEY = ""
DASHBOARD_URL = "http://localhost:3000"
CATEGORIES = ['massing', 'unit_study', 'facade', 'analysis', 'optimization', 'documentation', 'other']
def populate_value_list(nickname, values):
"""
Find a Value List component by NickName and populate it with values.
Value List NickName must match the given nickname.
Args:
nickname: NickName of the Value List component
values: List of values (str) or list of tuples (display_name, actual_value)
"""
try:
doc = ghenv.Component.OnPingDocument()
if not doc:
return False
for obj in doc.Objects:
if isinstance(obj, gh.Kernel.Special.GH_ValueList):
if obj.NickName == nickname:
# Clear existing items
obj.ListItems.Clear()
# Add new items
for val in values:
if isinstance(val, tuple):
# (display_name, actual_value) format
display_name, actual_value = val
item = gh.Kernel.Special.GH_ValueListItem(display_name, f'"{actual_value}"')
else:
# Simple string format
item = gh.Kernel.Special.GH_ValueListItem(val, f'"{val}"')
obj.ListItems.Add(item)
# Select first item
if obj.ListItems.Count > 0:
obj.SelectItem(0)
obj.ExpireSolution(False) # False to prevent infinite loop
return True
return False
except Exception as e:
print(f"Value List error: {e}")
return False
# ============================================
# Helper Functions
# ============================================
def make_request(endpoint, method="GET"):
"""Make HTTP request to Supabase"""
url = f"{SUPABASE_URL}{endpoint}"
headers = {
"apikey": SUPABASE_KEY,
"Authorization": f"Bearer {SUPABASE_KEY}",
"Content-Type": "application/json",
}
req = urllib.request.Request(url, headers=headers, method=method)
try:
with urllib.request.urlopen(req) as response:
return json.loads(response.read().decode('utf-8'))
except urllib.error.HTTPError as e:
error_body = e.read().decode('utf-8')
raise Exception(f"HTTP {e.code}: {error_body}")
def get_all_scripts(cat=None):
"""Get all scripts from Script Store"""
endpoint = "/rest/v1/scripts?select=*&order=download_count.desc"
if cat and cat in CATEGORIES:
endpoint += f"&category=eq.{cat}"
return make_request(endpoint)
def get_script_detail(sid):
"""Get specific script details"""
endpoint = f"/rest/v1/scripts?id=eq.{sid}&select=*"
result = make_request(endpoint)
if result and len(result) > 0:
return result[0]
return None
def get_script_versions(sid):
"""Get version history for a script"""
endpoint = f"/rest/v1/script_versions?script_id=eq.{sid}&select=*&order=created_at.desc"
return make_request(endpoint)
def format_script_list(script_list):
"""Format script list for display"""
if not script_list:
return "No scripts found"
lines = []
lines.append("=" * 60)
lines.append("SCRIPT STORE")
lines.append("=" * 60)
lines.append("")
for i, s in enumerate(script_list, 1):
lines.append(f"{i}. {s.get('name', 'Unnamed')}")
lines.append(f" Category: {s.get('category', '-')} | Version: v{s.get('version', '1.0.0')} | Downloads: {s.get('download_count', 0)}")
lines.append(f" ID: {s.get('id', '-')}")
if s.get('description'):
desc = s['description'][:80] + "..." if len(s.get('description', '')) > 80 else s.get('description', '')
lines.append(f" {desc}")
lines.append("")
lines.append("-" * 60)
lines.append(f"Total: {len(script_list)} scripts")
lines.append(f"Categories: {', '.join(CATEGORIES)}")
return "\n".join(lines)
def format_script_detail(script):
"""Format script detail for display"""
if not script:
return "Script not found"
lines = []
lines.append("=" * 60)
lines.append(f"SCRIPT: {script.get('name', 'Unnamed')}")
lines.append("=" * 60)
lines.append("")
lines.append(f"ID: {script.get('id', '-')}")
lines.append(f"Category: {script.get('category', '-')}")
lines.append(f"Version: v{script.get('version', '1.0.0')}")
lines.append(f"Author: {script.get('author', '-')}")
lines.append(f"Downloads: {script.get('download_count', 0)}")
lines.append("")
if script.get('description'):
lines.append("Description:")
lines.append(f" {script.get('description')}")
lines.append("")
if script.get('dependencies'):
deps = script.get('dependencies', [])
if deps:
lines.append(f"Dependencies: {', '.join(deps)}")
if script.get('tags'):
tags = script.get('tags', [])
if tags:
lines.append(f"Tags: {', '.join(tags)}")
lines.append("")
lines.append(f"Dashboard: {DASHBOARD_URL}/scripts/{script.get('id')}")
if script.get('file_url'):
lines.append(f"Download: {script.get('file_url')}")
return "\n".join(lines)
def format_versions(version_list, script_name):
"""Format version list for display"""
if not version_list:
return "No version history found"
lines = []
lines.append("=" * 60)
lines.append(f"VERSION HISTORY: {script_name}")
lines.append("=" * 60)
lines.append("")
for i, v in enumerate(version_list):
is_latest = " [LATEST]" if i == 0 else ""
lines.append(f"v{v.get('version', '?')}{is_latest}")
if v.get('changelog'):
lines.append(f" {v.get('changelog')}")
created = v.get('created_at', '')[:10] if v.get('created_at') else '-'
lines.append(f" Created: {created}")
if v.get('file_url'):
lines.append(f" File: Available")
lines.append("")
lines.append(f"Total versions: {len(version_list)}")
return "\n".join(lines)
def download_and_load_script(sid):
"""
Download script from Script Store and load into Grasshopper canvas.
WARNING: This will replace the current canvas!
Returns:
tuple: (success: bool, message: str)
"""
try:
import Grasshopper
except ImportError:
return (False, "Error: Not running in Grasshopper environment")
# Get script details
script = get_script_detail(sid)
if not script:
return (False, f"Error: Script not found: {sid}")
file_url = script.get('file_url')
if not file_url:
return (False, f"Error: No file available for script: {script.get('name', sid)}")
script_name = script.get('name', 'Unknown')
version = script.get('version', '1.0.0')
# Determine file extension from URL
ext = '.gh'
if file_url.lower().endswith('.ghx'):
ext = '.ghx'
# Download to temp folder
temp_dir = tempfile.gettempdir()
safe_name = "".join(c for c in script_name if c.isalnum() or c in (' ', '-', '_')).strip()
temp_path = os.path.join(temp_dir, f"{safe_name}_v{version}{ext}")
try:
urllib.request.urlretrieve(file_url, temp_path)
except Exception as e:
return (False, f"Error downloading: {str(e)}")
# Load into Grasshopper
try:
io = Grasshopper.Kernel.GH_DocumentIO()
if io.Open(temp_path):
doc = io.Document
Grasshopper.Instances.ActiveCanvas.Document = doc
# Increment download count
try:
increment_download(sid)
except:
pass # Don't fail if increment fails
return (True, f"Loaded: {script_name} v{version}\nPath: {temp_path}")
else:
return (False, f"Error: Failed to open GH file")
except Exception as e:
return (False, f"Error loading: {str(e)}")
def increment_download(sid):
"""Increment download count for a script"""
# Get current count
script = get_script_detail(sid)
if not script:
return
current_count = script.get('download_count', 0)
new_count = current_count + 1
# Update count
url = f"{SUPABASE_URL}/rest/v1/scripts?id=eq.{sid}"
headers = {
"apikey": SUPABASE_KEY,
"Authorization": f"Bearer {SUPABASE_KEY}",
"Content-Type": "application/json",
"Prefer": "return=minimal"
}
data = json.dumps({"download_count": new_count}).encode('utf-8')
req = urllib.request.Request(url, data=data, headers=headers, method="PATCH")
try:
urllib.request.urlopen(req)
except:
pass # Silently fail
# ============================================
# Execute
# ============================================
scripts = ""
script_ids = []
versions = ""
load_result = ""
# Input defaults
try:
load
except NameError:
load = False
try:
script_id
except NameError:
script_id = None
try:
category
except NameError:
category = None
# Auto-populate Value List component named "category"
populate_value_list("category", CATEGORIES)
try:
# Handle load request
if load and script_id:
success, message = download_and_load_script(script_id)
if success:
load_result = f"✅ {message}"
else:
load_result = f"❌ {message}"
# Note: After loading, the canvas will be replaced
# so these outputs won't be visible
scripts = "Loading script..."
script_ids = [script_id]
versions = ""
elif load and not script_id:
load_result = "⚠️ script_id is required to load a script"
scripts = "Set script_id and then set load=True"
script_ids = []
versions = ""
elif script_id:
# Show specific script details and versions
script_detail = get_script_detail(script_id)
scripts = format_script_detail(script_detail)
script_ids = [script_id] if script_detail else []
# Get versions
version_list = get_script_versions(script_id)
script_name = script_detail.get('name', 'Unknown') if script_detail else 'Unknown'
versions = format_versions(version_list, script_name)
# Show load hint
if script_detail and script_detail.get('file_url'):
load_result = "💡 Set load=True to load this script (replaces current canvas!)"
else:
load_result = "⚠️ No file available for this script"
else:
# List all scripts
script_list = get_all_scripts(category)
scripts = format_script_list(script_list)
script_ids = [s.get('id') for s in script_list] if script_list else []
versions = "Select a script from 'Scripts' Value List"
load_result = "Select a script, then set load=True"
# Populate "Scripts" Value List with script names and IDs
if script_list:
script_items = [
(f"{s.get('name', 'Unnamed')} (v{s.get('version', '1.0')})", s.get('id'))
for s in script_list
]
populate_value_list("Scripts", script_items)
except Exception as e:
scripts = f"Error: {str(e)}"
script_ids = []
versions = ""
load_result = f"Error: {str(e)}"