-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patheditorInterface.py
576 lines (497 loc) · 23.4 KB
/
editorInterface.py
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
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
from cmu_graphics import *
from ShapeObject import ShapeObject
from FaceObject import FaceObject
from shapeCollectionObject import shapeCollectionObject
from buttonHandler import ButtonHandler
import math
import random
import re
def onAppStart(app):
app.mode = "viewport"
app.buttonList = []
app.defaultNewShape = (1,2)
initiateWelcome(app)
initiateEditor(app)
def initiateWelcome(app):
app.collection = shapeCollectionObject()
app.keyDisabled = False
#app.collection.addShape(ShapeObject((0,0,0),1,0))
def initiateEditor(app):
app.selectedDotIndex = (0,0)
app.camTheta = (45, 45, 45)
app.r = 50
app.keyDisabled = False
app.viewportCenter = (app.width/2,app.height/2)
app.editorWidth = 250
app.viewport_point_List = {}
updateViewport(app)
def inputEditorButton(app, mouseX, mouseY):
minusX = 850
minusY = 270
for i in range (3):
currentminusY = minusY + 50 * i
if distance(mouseX, mouseY, minusX, currentminusY) < 15:
return (i, -1)
elif distance(mouseX, mouseY, minusX+100, currentminusY) < 15:
return (i, +1)
return (-1, 0) # No button clicked
def onMousePress(app, mouseX, mouseY):
eventHandler(app) # Update the button list based on the current mode
if app.mode == "viewport":
for button in app.buttonList:
if button.isClicked(mouseX, mouseY):
app.mode = "editorMan"
elif app.mode == "editorMan":
shapeClicked = False
# Handle shape button clicks
for button in app.buttonList:
if button.isClicked(mouseX, mouseY):
if button.buttonType == "circle" and button.name == "addshape" and len(app.collection.shapes) <= 1:
app.mode = "editorAddShape"
elif button.buttonType == "circle" and button.name == "merge" and len(app.collection.shapes) == 2 and app.collection.canMerge():
app.collection.mergeAll()
app.selectedDotIndex = (0,0)
app.mode = "editorMan"
elif button.buttonType == "circle" and button.name == "deleteshape":
print(len(app.collection.shapes))
app.collection.removeShape(app.selectedDotIndex[0])
app.selectedDotIndex = (0,0)
elif button.buttonType == "circle" and button.name == "exit":
app.mode = "viewport"
elif button.buttonType == "rectangle" and button.name == "shapeSelector":
app.selectedDotIndex = (button.shapeIndex, app.selectedDotIndex[1])
shapeClicked = True
break
if not shapeClicked:
# Handle clicks on shape points
for shapeIndex, viewPoints in app.viewport_point_List.items():
for dotIndex, (cx, cy) in enumerate(viewPoints):
if distance(mouseX, mouseY, cx, cy) < 5:
app.selectedDotIndex = (shapeIndex, dotIndex)
shapeClicked = True
break
if shapeClicked:
break
inputIdx = inputEditorButton(app, mouseX, mouseY)
if inputIdx[0] != -1:
print("\t"*5,inputIdx)
shapeIndex, dotIndex = app.selectedDotIndex
shape = app.collection.shapes[shapeIndex]
x, y, z = shape.points[dotIndex]
if inputIdx[0] == 0:
newPoint = (x + inputIdx[1], y, z)
elif inputIdx[0] == 1:
newPoint = (x, y + inputIdx[1], z)
else:
newPoint = (x, y, z + inputIdx[1])
shape.points[dotIndex] = newPoint
shape.rearrangeFaces()
elif app.mode == "editorAddShape":
for button in app.buttonList:
if button.isClicked(mouseX, mouseY):
print(button.buttonType, button.name)
if button.buttonType == "rectangle" and button.name == "Generate shape via AI":
app.mode = "loading"
userInput = app.getTextInput("Enter a description of a 3D shape (e.g., '3D octagonal prism') - the more detailed the better!")
parsedThing = retrievingJson(userInput)
if parsedThing != None:
points, order = parsedThing
print("this is the points", points)
print("this is the order", order)
newShape = ShapeObject((0,0,0), points, order)
app.collection.addShape(newShape)
app.mode = "editorMan"
else:
print("BAD BAD BAD")
elif button.buttonType == "rectangle" and button.name == "Create!":
app.collection.addShape(ShapeObject((0, 0, 0), *app.defaultNewShape))
app.mode = "editorMan"
elif button.buttonType == "rectangle" and type(button.name) == tuple:
app.defaultNewShape = button.name
elif app.mode == "ERROR":
pass
updateViewport(app)
def parse_points_and_order(input_string):
# Find the start and end indices for the "points" and "order" sections
points_start = input_string.find('"points": [') + len('"points": [')
points_end = input_string.find(']', points_start)
order_start = input_string.find('"order": [') + len('"order": [')
order_end = input_string.rfind(']')
# Extract the substrings containing the points and order
points_str = input_string[points_start:points_end]
order_str = input_string[order_start:order_end]
# Parse points: Split by "), (" and remove the surrounding parentheses and extra spaces
points = []
points_list = points_str.split('), (')
for point in points_list:
# Remove any surrounding spaces and parentheses, then convert to a tuple of floats
point = point.strip(' ()')
point_tuple = tuple(map(float, point.split(',')))
points.append(point_tuple)
# Parse order: Split by '], [' and convert to lists of integers
order = []
order_list = order_str.split('], [')
for group in order_list:
# Remove any surrounding spaces and brackets, then split by commas and convert to integers
group = group.strip(' []')
group_list = list(map(int, group.split(',')))
order.append(group_list)
print(group_list)
return points, order
def retrievingJson(user_input):
print("Welcome to the 3D Shape Generator!")
result = generate_3d_shape(user_input)
print(result)
try:
if "points" in result and "order" in result:
points, order = parse_points_and_order(result)
print(points, order)
return points, order
else:
print("\nThe response contains guidelines or an unexpected format:")
print(result)
except json.JSONDecodeError:
# Print guidelines or plain text if the result is not JSON
print("\nThe response from OpenAI:")
print(result)
def eventHandler(app):
app.buttonList = []
if len(app.collection.shapes) == 0:
app.mode = "editorAddShape"
if len(app.collection.shapes) == 1:
app.collection.shapes[0].moveCenter((0,0,0))
if app.mode == "viewport":
app.keyDisabled = False
editor_button = ButtonHandler(
buttonType="circle",
cx=app.width - 40,
cy=40,
radius=25,
name="editor"
)
app.buttonList.append(editor_button)
elif app.mode == "editorMan":
app.keyDisabled = False
exit_button = ButtonHandler(
buttonType="circle",
cx=app.width - 40 - app.editorWidth,
cy=40,
radius=25,
name="exit"
)
app.buttonList.append(exit_button)
add_button = ButtonHandler(
buttonType="circle",
cx=app.width - 40 - app.editorWidth,
cy=100,
radius=25,
name="addshape"
)
app.buttonList.append(add_button)
delete_button = ButtonHandler(
buttonType="circle",
cx=app.width - 40 - app.editorWidth,
cy=160,
radius=25,
name="deleteshape"
)
app.buttonList.append(delete_button)
merge_button = ButtonHandler(
buttonType="circle",
cx=app.width - 40 - app.editorWidth,
cy=220,
radius=25,
name="merge"
)
app.buttonList.append(merge_button)
# Add shape selector buttons
button_width = (app.editorWidth - 60)
button_height = 50
x_start = app.width - app.editorWidth + 20
y_start = 80
for i, shape in enumerate(app.collection.shapes):
shape_button = ButtonHandler(
buttonType="rectangle",
x=x_start + i * (button_width + 10),
y=y_start,
width=button_width,
height=button_height,
name="shapeSelector",
shapeIndex=i
)
app.buttonList.append(shape_button)
elif app.mode == "editorAddShape":
panelX = app.width - app.editorWidth
# Standard Shapes Buttons
categoryY = 170
standardShapes = ["Pyramid", "Cube"]
buttonHeight = 30
shapeY = categoryY + 30
for i, shape in enumerate(standardShapes):
textWidth = len(shape) * 10
button = ButtonHandler(
buttonType="rectangle",
x=panelX + 20,
y=shapeY + i * (buttonHeight + 5),
width=textWidth,
height=buttonHeight,
name=(0,i)
)
app.buttonList.append(button)
# Prisms Buttons
prismY = shapeY + len(standardShapes) * (buttonHeight + 5) + 20
prismShapes = ["Triangular", "Square", "Hexagonal", "Pentagonal"]
prismY += 30
for i, shape in enumerate(prismShapes):
textWidth = len(shape) * 10
button = ButtonHandler(
buttonType="rectangle",
x=panelX + 20,
y=prismY + i * (buttonHeight + 5),
width=textWidth,
height=buttonHeight,
name=(1,i)
)
app.buttonList.append(button)
# Confirm Button
confirmY = prismY + len(prismShapes) * (buttonHeight + 5) + 20
confirmButton = ButtonHandler(
buttonType="rectangle",
x=panelX + 20,
y=confirmY,
width=app.editorWidth,
height=40,
name="Generate shape via AI"
)
app.buttonList.append(confirmButton)
# Confirm Button
confirmButton = ButtonHandler(
buttonType="rectangle",
x=panelX + 20,
y=app.height-200,
width=app.editorWidth +100,
height=40,
name="Create!"
)
app.buttonList.append(confirmButton)
def redrawAll(app):
print(app.mode)
for s in app.collection.shapes:
print(s)
if len(app.collection.shapes) == 0:
drawViewport(app)
drawRect(0, 0, app.width, app.height, fill="black", opacity=50)
drawAddShapePanel(app)
else:
if app.mode == "loading":
drawRect(0, 0, app.width, app.height, fill="black", opacity=50)
drawLabel("Loading API response....", app.width/2, app.height/2, size = 30, fill = "white")
elif app.mode == "viewport":
drawViewport(app)
drawCircle(app.width - 40, 40, 25, fill='lightSkyBlue')
drawLabel("Editor", app.width - 40, 40, size=12, bold=True, fill="darkblue")
elif app.mode == "editorMan":
drawViewport(app)
drawCircle(app.width - 40 - app.editorWidth, 40, 25, fill="Salmon")
drawLabel("Exit", app.width - 40 - app.editorWidth, 40, size=12, bold=True, fill="maroon")
#can't add a shape when there are 2 or more shapes already
if len(app.collection.shapes) >= 2:
drawCircle(app.width - 40 - app.editorWidth, 100, 25, fill='lightGray')
drawLabel("Add", app.width - 40 - app.editorWidth, 100, size=12, bold=True, fill="Gray")
else:
drawCircle(app.width - 40 - app.editorWidth, 100, 25, fill='lightSkyBlue')
drawLabel("Add", app.width - 40 - app.editorWidth, 100, size=12, bold=True, fill="darkblue")
#merge button
if len(app.collection.shapes) == 2 and app.collection.canMerge():
print("why is this not printing")
print("this is the shared list", app.collection.getSharedFaces())
drawCircle(app.width - 40 - app.editorWidth, 220, 25, fill='lightSkyBlue')
drawLabel("Merge", app.width - 40 - app.editorWidth, 220, size=12, bold=True, fill="darkblue")
else:
drawCircle(app.width - 40 - app.editorWidth, 220, 25, fill='lightGray')
drawLabel("Merge", app.width - 40 - app.editorWidth, 220, size=12, bold=True, fill="Gray")
drawCircle(app.width - 40 - app.editorWidth, 160, 25, fill='lightSkyBlue')
drawLabel("Delete", app.width - 40 - app.editorWidth, 160, size=12, bold=True, fill="darkblue")
drawEditorForManipulation(app)
elif app.mode == "editorAddShape":
drawViewport(app)
drawRect(0, 0, app.width, app.height, fill="black", opacity=50)
drawAddShapePanel(app)
def drawAddShapePanel(app):
panelX = app.width - app.editorWidth
drawRect(panelX, 0, app.editorWidth, app.height, fill=rgb(128, 196, 233), opacity=100)
drawLabel("Editor", panelX + app.editorWidth / 2, 40, size=25, bold=True)
drawLabel("Add New Shape", panelX + app.editorWidth / 2, 80, size=20, bold=True)
centerY = 120
drawLabel("Spawn Point:", panelX + 20, centerY, size=14, align="left", bold=True)
spawn = app.collection.getNewSpawnPoint(ShapeObject((0, 0, 0), *app.defaultNewShape))
drawLabel(spawn, panelX + 150, centerY, size=14, bold=True)
# Standard Shapes Section
categoryY = centerY + 50
drawLabel("Standard Shapes", panelX + 20, categoryY, size=16, bold=True, align="left")
standardShapes = ["Pyramid", "Cube"]
buttonHeight = 30
shapeY = categoryY + 30
for i, shape in enumerate(standardShapes):
textWidth = len(shape) * 10
fill = rgb(255, 246, 233)
if app.defaultNewShape == (0,i):
fill = "orange"
drawRect(panelX + 20, shapeY + i * (buttonHeight + 5), textWidth, buttonHeight, fill=fill)
drawLabel(shape, panelX + 20 + textWidth / 2, shapeY + i * (buttonHeight + 5) + buttonHeight / 2, size=12, bold=True)
# Prisms Section
prismY = shapeY + len(standardShapes) * (buttonHeight + 5) + 20
drawLabel("Prisms", panelX + 20, prismY, size=16, bold=True, align="left")
prismShapes = ["Triangular", "Square", "Hexagonal", "Pentagonal"]
prismY += 30
for i, shape in enumerate(prismShapes):
textWidth = len(shape) * 10
fill = rgb(255, 246, 233)
if app.defaultNewShape == (1,i):
fill = "orange"
drawRect(panelX + 20, prismY + i * (buttonHeight + 5), textWidth, buttonHeight, fill=fill)
drawLabel(shape, panelX + 20 + textWidth / 2, prismY + i * (buttonHeight + 5) + buttonHeight / 2, size=12, bold=True)
confirmY = prismY + len(prismShapes) * (buttonHeight + 5) + 20
drawRect(panelX + 20, confirmY, app.editorWidth - 40, 40, fill=rgb(255, 127, 62), border="black", borderWidth=2)
drawLabel("Generate shape via AI", panelX + app.editorWidth / 2, confirmY + 20, size=14, bold=True, fill="white")
# Confirm Button
confirmY = prismY + len(prismShapes) * (buttonHeight + 5) + 20
drawRect(panelX + 20, app.height-200, app.editorWidth - 40, 40, fill=rgb(255, 127, 62), border="black", borderWidth=2)
drawLabel("Create!", panelX + app.editorWidth / 2, app.height-180, size=14, bold=True, fill="white")
def drawEditorForManipulation(app):
drawRect(app.width - app.editorWidth, 0, app.editorWidth, app.height, fill="powderBlue", opacity=80)
drawLabel("Editor", app.width - app.editorWidth + 120, 40, size=35)
shapeIndex, dotIndex = app.selectedDotIndex
selectedShape = app.collection.shapes[shapeIndex]
selectedPoint = selectedShape.points[dotIndex]
selectFaces = selectedShape.getFacesAdjacentToPoint(dotIndex)
y_start = 80
x_start = app.width - app.editorWidth + 40
button_width = (app.editorWidth - 100) // len(app.collection.shapes)
button_height = 50
highlightColor = "white"
thickness = 2
for i, shape in enumerate(app.collection.shapes):
x = x_start + i * (button_width + 40)
if shapeIndex != i: highlightColor = None
else: highlightColor = "white"
drawCircle(x + 10, y_start + button_height / 2, button_height / 2, fill="skyblue",border = highlightColor, borderWidth = thickness)
drawCircle(x + button_width - 10, y_start + button_height / 2, button_height / 2, fill="skyblue",border = highlightColor, borderWidth = thickness)
drawRect(x + 10, y_start, button_width - 20, button_height, fill="skyblue")
if shapeIndex == i:
drawLine(x + 10, y_start+button_height, x + 10+button_width - 20, y_start+button_height, fill=highlightColor, lineWidth=thickness)
drawLine(x + 10, y_start, x + 10+button_width - 20, y_start, fill=highlightColor,lineWidth=thickness)
drawLabel(f"Shape {i + 1}", x + button_width / 2, y_start + button_height / 2, size=14, bold=True)
y_point = y_start + 100
drawLabel(f"This selected point is in Faces", app.width - app.editorWidth + 20, y_point+15, size=14, align="left")
faceListstr = ", ".join(map(str, selectFaces))
drawLabel(f"{faceListstr}", app.width - app.editorWidth + 20, y_point + 20+20, size=16, align="left")
coordList = ["X", "Y", "Z"]
for i, coord in enumerate(coordList):
y = y_point+30 + 60 + i * 50
drawLabel(coord, app.width - app.editorWidth + 40, y, size=16, bold=True)
drawCircle(app.width - app.editorWidth + 100, y, 15, fill="lightsalmon")
drawLabel("-", app.width - app.editorWidth + 100, y, size=14, bold=True, fill="red")
drawCircle(app.width - app.editorWidth + 200, y, 15, fill="lightgreen")
drawLabel(int(selectedPoint[i]), app.width - app.editorWidth + 150, y, size=20, bold=True, fill="black")
drawLabel("+", app.width - app.editorWidth + 200, y, size=14, bold=True, fill="green")
##############################################
############################################## MISC
##############################################
def transformToViewport(app, point):
thetaX, thetaY, thetaZ = app.camTheta # Unpack rotation angles
x, y, z = point
rotated_x = x * math.cos(math.radians(thetaZ)) - y * math.sin(math.radians(thetaZ))
rotated_y = x * math.sin(math.radians(thetaZ)) + y * math.cos(math.radians(thetaZ))
# Apply y-axis rotation
rotated_x = rotated_x * math.cos(math.radians(thetaY)) + z * math.sin(math.radians(thetaY))
rotated_z = -rotated_x * math.sin(math.radians(thetaY)) + z * math.cos(math.radians(thetaY))
# Apply x-axis rotation
final_x = rotated_x * math.cos(math.radians(thetaX)) - rotated_y * math.sin(math.radians(thetaX))
final_y = rotated_x * math.sin(math.radians(thetaX)) + rotated_y * math.cos(math.radians(thetaX))
# Translate to the center of the screen for viewport
viewport_x = app.viewportCenter[0] + final_x * app.r # Scaling for visibility
viewport_y = app.viewportCenter[1] + final_y * app.r
return (viewport_x, viewport_y)
def onKeyHold(app, key):
if not app.keyDisabled:
thetaX, thetaY, thetaZ = app.camTheta
if "right" in key:
app.camTheta = (thetaX + 2, thetaY, thetaZ)
elif "left" in key:
app.camTheta = (thetaX - 2, thetaY, thetaZ)
elif "up" in key:
app.camTheta = (thetaX, (thetaY + 2) % 360, thetaZ)
elif "down" in key:
app.camTheta = (thetaX, (thetaY - 2) % 360, thetaZ)
elif "w" in key:
app.camTheta = (thetaX, thetaY, (thetaZ + 2) % 360)
elif "s" in key:
app.camTheta = (thetaX, thetaY, (thetaZ - 2) % 360)
if "+" in key or "=" in key:
app.r += 10
elif "-" in key or "_" in key:
app.r -= 10
updateViewport(app)
def updateViewport(app):
app.viewport_point_List = {}
for j, shape in enumerate(app.collection.shapes):
tempShapeviewList = []
for i in range(len(shape.points)):
point = shape.points[i]
viewport_point = transformToViewport(app, point)
tempShapeviewList.append(viewport_point)
app.viewport_point_List[j] = tempShapeviewList
def drawViewport(app):
for shapeIndex, viewPoints in app.viewport_point_List.items():
for dotIndex, point in enumerate(viewPoints):
fill = "lightSalmon"
if (shapeIndex, dotIndex) == app.selectedDotIndex:
fill = "red"
drawCircle(*point, 5, fill=fill)
shape = app.collection.shapes[shapeIndex]
for face in shape.faces:
polygon = [(viewPoints[vertexIndex][0], viewPoints[vertexIndex][1]) for vertexIndex in face.order]
drawPolygon(*[coord for vertex in polygon for coord in vertex], fill=rgb(*face.color), opacity=50)
for edge in face.getEdges():
drawLine(*viewPoints[edge[0]], *viewPoints[edge[1]])
import requests
import json
# Your OpenAI API key
API_KEY = "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"
# OpenAI API endpoint for chat completions
API_URL = "https://api.openai.com/v1/chat/completions"
def generate_3d_shape(user_input):
"""
Sends a request to OpenAI to generate a 3D shape based on user input.
:param user_input: A description of the desired 3D shape.
:return: JSON response containing points and order or guidelines for improvement.
"""
# Prepare the request payload
payload = {
"model": "gpt-4",
"messages": [
{
"role": "user",
"content": (
f"You are a 3D shape generator assistant. The user wants to generate a 3D shape based on the following description: {user_input}. If the description is sufficient, respond strictly with the JSON representation of the shape in the following format: {{ \"points\": [(x1, y1, z1), (x2, y2, z2), ...], \"order\": [[index1, index2, index3, ...], ...] }}. Do not include any extra text, explanations, or instructions. If the description is insufficient or invalid, respond only with 'Invalid description'."
)
}
],
"temperature": 0.7
}
# Set up headers for authentication and content type
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}"
}
# Send the POST request to the OpenAI API
response = requests.post(API_URL, headers=headers, json=payload)
# Handle the response
if response.status_code == 200:
result = response.json()
content = result["choices"][0]["message"]["content"]
return content
else:
return f"Error: {response.status_code} - {response.text}"
runApp(width=1000, height=800)