-
Notifications
You must be signed in to change notification settings - Fork 1
/
basics.py
396 lines (355 loc) · 10.8 KB
/
basics.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
import bpy
import bmesh
from phaenotyp import geometry
from queue import Queue
from time import time
from datetime import timedelta
import uuid
blender_version = (4,0,2)
phaenotyp_version = (0,3,0)
phaenotyp_name = (
"Phänotyp "
+ str(phaenotyp_version[0]) + "."
+ str(phaenotyp_version[1]) + "."
+ str(phaenotyp_version[2])
)
jobs = [] # list to store jobs to be calculated
jobs_total = 0 # amount of jobs in total
jobs_percentage = 0 # percentage of jobs done
time_started = 0 # first stop started at
time_elapsed = 0 # time elapsed since first job
time_left = 0 # time left based on elapsed time
is_running_jobs = False
terminal = ["", "", "", "", "", "", "", "", ""]
def print_data(text):
"""
Print data for debugging
:param text: Needs a text as string (Do not pass as list)
"""
text = "Phaenotyp | " + text
terminal.append(text)
if len(terminal) > 10:
terminal.pop(0)
print(text)
class timer:
"""
Class to handle the timer
"""
start_time = None
@staticmethod
def start():
"""
Start the timer
"""
timer.start_time = time()
@staticmethod
def stop():
"""
Return the elapsed time
:return: Formated time in seconds as string
"""
elapsed = time() - timer.start_time
return " | " + str(timedelta(seconds=elapsed))
def create_data():
"""
Create scene[<Phaenotyp>] and build all basics to store data.
All data that should be saved with the blend-file is stored here.
Data that does not need to be available after restart is generated,
handeled and stored by the specific function or class.
"""
data = bpy.context.scene["<Phaenotyp>"] = {
"scene_id": str(uuid.uuid4()),
"structure": None,
"supports": {},
"nodes": {},
"members": {},
"quads": {},
"frames": {},
"loads_v": {},
"loads_e": {},
"loads_f": {},
"process": {
"scipy_available": False,
"version": phaenotyp_version
},
"done": {},
"environment": {},
"individuals": {},
"panel_state": {
"structure": False,
"calculation_type": False,
"supports": False,
"members": False,
"quads": False,
"file": False
},
"panel_grayed": {
"scipy": False,
"calculation_type": False,
"supports": False,
"members": False,
"quads": False,
"loads": False
},
"texts": {},
"precast": {}
}
def sorted_keys(dict):
'''
Is sorting the keys of the dict (to avoid iterating like 0,10,2,3 ...)
:param dict dict: Dictionary with keys as string
:return: Sorted keys as integer
'''
keys_int = list(map(int, dict))
sorted_int_keys = sorted(keys_int)
return sorted_int_keys
def avoid_div_zero(a,b):
'''
To avoid division by zero if a force is 0.
:param a: Can be integer or float
:param b: Can be integer or float
:return: Returns the result or 0 in case of division by zero.
'''
if b == 0:
return 0
else:
return a/b
def return_max_diff_to_zero(list):
'''
Return the value with the highest difference to zero (for plus or minus)
:param list: List of integers or floats
:return: List item of given list
'''
list_copy = list.copy()
list_copy.sort()
smallest_minus = list_copy[0]
biggest_plus = list_copy[len(list_copy)-1]
if abs(smallest_minus) > abs(biggest_plus):
return smallest_minus
else:
return biggest_plus
def delete_obj_if_existing(name):
'''
Delete object with given name if existing
:param name: Name as string
'''
obj = bpy.data.objects.get(name)
if obj:
bpy.data.objects.remove(obj, do_unlink=True)
def delete_mesh_if_existing(name):
'''
Delete mesh with given name if existing
:param name: Name as string
'''
mesh = bpy.data.meshes.get(name)
if mesh:
bpy.data.meshes.remove(mesh, do_unlink=True)
def delete_col_if_existing(name):
'''
Delete collection with given name if existing
:param name: Name as string
'''
col = bpy.data.collections.get(name)
if col:
bpy.data.collections.remove(col, do_unlink=True)
def delete_obj_if_name_contains(text):
'''
Delete objectif name contains the given string.
:param name: Name as string
'''
for obj in bpy.data.objects:
if text in obj.name_full:
bpy.data.objects.remove(obj, do_unlink=True)
def view_wireframe():
'''
Change view to show colored material and hide structure.
'''
bpy.context.space_data.shading.type = 'WIREFRAME'
def view_vertex_colors():
'''
Change view to show colored material and hide structure.
'''
bpy.context.space_data.shading.type = 'MATERIAL'
# hide structure
try:
data = bpy.context.scene["<Phaenotyp>"]
obj = data["structure"]
obj.hide_set(True)
obj.hide_render = True
# go to object-mode to avoid confusion
bpy.ops.object.mode_set(mode="OBJECT")
except:
pass
def revert_vertex_colors():
'''
Change view to solid.
'''
bpy.context.space_data.shading.type = 'SOLID'
# data is no longer available after reset
# therefore the obj is made visible again allready in reset
# try, if the user has deleted the object
try:
# go to object-mode to avoid confusion
bpy.ops.object.mode_set(mode="OBJECT")
except:
pass
def popup(title = "Phaenotyp", lines=""):
'''
Create popup to inform user. The function is based on the answer from ChameleonScales at:
https://blender.stackexchange.com/questions/169844/multi-line-text-box-with-popup-menu
:param lines: List of strings to be written.
'''
def draw(self, context):
for line in lines:
self.layout.label(text=line)
bpy.context.window_manager.popup_menu(draw, title = title)
def popup_operator(title = "Phaenotyp", lines="", operator=None, text=""):
'''
Create popup to inform user and to run an operator. Based on the answer from ChameleonScales at:
https://blender.stackexchange.com/questions/169844/multi-line-text-box-with-popup-menu
:param lines: List of strings to be written.
:param operator: Operator to start.
:param text: Name of the operator.
'''
def draw(self, context):
for line in lines:
self.layout.label(text=line)
self.layout.separator()
self.layout.operator(operator, text=text)
bpy.context.window_manager.popup_menu(draw, title = title)
def force_distribution_info(self, context):
'''
Create a popup to inform user about force disbribution.
'''
# inform user when using force_distribution
if bpy.context.scene.phaenotyp.calculation_type == "force_distribution":
# triangulation
if geometry.triangulation() == False:
text = ["The selection needs to be triangulated for force distribution.",
"Should Phaenotyp try to triangulate the selection?"]
popup_operator(lines=text, operator="wm.fix_structure", text="Triangulate")
geometry.to_be_fixed = "triangulate"
else:
text = [
"Force distribution is a solver for advance users.",
"Please make sure, that your structure meets this conditions:",
"- the mesh is triangulated",
"- the structure is stable (not flat)",
"- exactly three vertices are defined as support",
"- the supports are not connected with egdes",
"- at least one load is defined"
]
popup(lines = text)
# check modifieres in modify or deform
# modifieres working with Phänotyp:
modifiers = {}
modifiers["ARMATURE"] = True
modifiers["CAST"] = True
modifiers["CLOTH"] = True
modifiers["COLLISION"] = True
modifiers["CURVE"] = True
modifiers["DATA_TRANSFER"] = True
modifiers["DYNAMIC_PAINT"] = True
modifiers["DISPLACE"] = True
modifiers["HOOK"] = True
modifiers["LAPLACIANDEFORM"] = True
modifiers["LATTICE"] = True
modifiers["MESH_CACHE"] = True
modifiers["MESH_DEFORM"] = True
modifiers["MESH_SEQUENCE_CACHE"] = True
modifiers["NORMAL_EDIT"] = True
modifiers["NODES"] = True
modifiers["SHRINKWRAP"] = True
modifiers["SIMPLE_DEFORM"] = True
modifiers["SMOOTH"] = True
modifiers["CORRECTIVE_SMOOTH"] = True
modifiers["LAPLACIANSMOOTH"] = True
modifiers["OCEAN"] = True
modifiers["PARTICLE_INSTANCE"] = True
modifiers["PARTICLE_SYSTEM"] = True
modifiers["SOFT_BODY"] = True
modifiers["SURFACE"] = True
modifiers["SURFACE_DEFORM"] = True
modifiers["WARP"] = True
modifiers["WAVE"] = True
modifiers["WEIGHTED_NORMAL"] = True
modifiers["UV_PROJECT"] = True
modifiers["UV_WARP"] = True
modifiers["VERTEX_WEIGHT_EDIT"] = True
modifiers["VERTEX_WEIGHT_MIX"] = True
modifiers["VERTEX_WEIGHT_PROXIMITY"] = True
# not working:
modifiers["ARRAY"] = False
modifiers["BEVEL"] = False
modifiers["BOOLEAN"] = False
modifiers["BUILD"] = False
modifiers["DECIMATE"] = False
modifiers["EDGE_SPLIT"] = False
modifiers["EXPLODE"] = False
modifiers["FLUID"] = False
modifiers["MASK"] = False
modifiers["MIRROR"] = False
modifiers["MESH_TO_VOLUME"] = False
modifiers["MULTIRES"] = False
modifiers["REMESH"] = False
modifiers["SCREW"] = False
modifiers["SKIN"] = False
modifiers["SOLIDIFY"] = False
modifiers["SUBSURF"] = False
modifiers["TRIANGULATE"] = False
modifiers["VOLUME_TO_MESH"] = False
modifiers["WELD"] = False
modifiers["WIREFRAME"] = False
modifiers["VOLUME_DISPLACE"] = False
def check_modifiers():
'''
Check the available modifiers of the object an give feedback.
A popup is opened if a modifier is available that could create weird results.
'''
obj = bpy.context.object
for modifiere in obj.modifiers:
name = modifiere.type
if name == "NODES":
text = ["Geometry Nodes can be used but make sure that no geometry is added",
"or deleted during execution of Phaenotyp to avoid weird results"]
popup(lines = text)
elif name in modifiers:
working = modifiers[name]
if working == False:
text = [
"Modifiere with type " + str(name) + " can cause weird results.",
"",
"You can use this modifiers:",
"ARMATURE, CAST, CLOTH, COLLISION, CURVE, DATA_TRANSFER,",
"DYNAMIC_PAINT, DISPLACE, HOOK, LAPLACIANDEFORM, LATTICE,",
"MESH_CACHE, MESH_DEFORM, MESH_SEQUENCE_CACHE, NORMAL_EDIT,",
"NODES, SHRINKWRAP, SIMPLE_DEFORM, SMOOTH, CORRECTIVE_SMOOTH,",
"LAPLACIANSMOOTH, OCEAN, PARTICLE_INSTANCE, PARTICLE_SYSTEM,",
"SOFT_BODY, SURFACE, SURFACE_DEFORM, WARP, WAVE, WEIGHTED_NORMAL,",
"UV_PROJECT, UV_WARP, VERTEX_WEIGHT_EDIT, VERTEX_WEIGHT_MIX,",
"VERTEX_WEIGHT_PROXIMITY."
]
popup(lines = text)
def set_selection_for_load(self, context):
'''
Switch type of selection according to type of load (VERT, EDGE, FACE)
'''
scene = context.scene
phaenotyp = scene.phaenotyp
# switch selection mode
if phaenotyp.load_type == "vertices":
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
if phaenotyp.load_type == "edges":
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
if phaenotyp.load_type == "faces":
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
def remap(value, left_min, left_max, right_min, right_max):
'''
Map the input from one domain to another just like remap domain in Rhino or map in Arduino
'''
# based on answer from Adam Luchjenbroers
# https://stackoverflow.com/questions/1969240/mapping-a-range-of-values-to-another
left_span = left_max - left_min
right_span = right_max - right_min
value_scaled = float(value - left_min) / float(left_span)
return right_min + (value_scaled * right_span)