├── README.md ├── __init__.py ├── blender_utils.py ├── previewMesh.py └── utils.py /README.md: -------------------------------------------------------------------------------- 1 | swiftBlock 2 | ========== 3 | Blender 3D addon for creating blockMeshDict files for OpenFOAM's blockMesh application. Find more info on openfoamwiki.net. Compatible with Blender 3D 2.70 - 2.72 4 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name": "SwiftBlock", 3 | "author": "Karl-Johan Nogenmyr", 4 | "version": (0, 1), 5 | "blender": (2, 6, 6), 6 | "api": 44000, 7 | "location": "Tool Shelf", 8 | "description": "Writes block geometry as blockMeshDict file", 9 | "warning": "not much tested yet", 10 | "wiki_url": "http://openfoamwiki.net/index.php/SwiftBlock", 11 | "tracker_url": "", 12 | "support": 'COMMUNITY', 13 | "category": "OpenFOAM"} 14 | 15 | #---------------------------------------------------------- 16 | # File scene_props.py 17 | #---------------------------------------------------------- 18 | import bpy 19 | from bpy.props import * 20 | 21 | def getPolyLines(verts, edges, obj): 22 | scn = bpy.context.scene 23 | polyLinesPoints = [] 24 | polyLines = '' 25 | polyLinesLengths = [[], []] 26 | 27 | def isPointOnEdge(point, A, B): 28 | eps = (((A - B).magnitude - (point-B).magnitude) - (A-point).magnitude) 29 | return True if (abs(eps) < scn.tol) else False 30 | 31 | nosnap= [False for i in range(len(edges))] 32 | for eid, e in enumerate(obj.data.edges): 33 | nosnap[eid] = e.use_edge_sharp 34 | 35 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(True,False,False)") 36 | geoobj = bpy.data.objects[scn.geoobjName] 37 | geo_verts = list(blender_utils.vertices_from_mesh(geoobj)) 38 | geo_edges = list(blender_utils.edges_from_mesh(geoobj)) 39 | geoobj.select = False # avoid deletion 40 | 41 | # First go through all vertices in the block structure and find vertices snapped to edges 42 | # When found, add a vertex at that location to the polyLine object by splitting the edge 43 | # Create a new Blender object containing the newly inserted verts. Then use Blender's 44 | # shortest path algo to find polyLines. 45 | 46 | for vid, v in enumerate(verts): 47 | found = False 48 | for gvid, gv in enumerate(geo_verts): 49 | mag = (v-gv).magnitude 50 | if mag < scn.tol: 51 | found = True 52 | break # We have found a vertex co-located, continue with next block vertex 53 | if not found: 54 | for geid, ge in enumerate(geo_edges): 55 | if (isPointOnEdge(v, geo_verts[ge[0]], geo_verts[ge[1]])): 56 | geo_verts.append(v) 57 | geo_edges.append([geo_edges[geid][1],len(geo_verts)-1]) # Putting the vert on the edge, by splitting it in two. 58 | geo_edges[geid][1] = len(geo_verts)-1 59 | break # No more iteration, go to next block vertex 60 | 61 | mesh_data = bpy.data.meshes.new("deleteme") 62 | mesh_data.from_pydata(geo_verts, geo_edges, []) 63 | mesh_data.update() 64 | geoobj = bpy.data.objects.new('deleteme', mesh_data) 65 | bpy.context.scene.objects.link(geoobj) 66 | geo_verts = list(blender_utils.vertices_from_mesh(geoobj)) 67 | geo_edges = list(blender_utils.edges_from_mesh(geoobj)) 68 | bpy.context.scene.objects.active=geoobj 69 | 70 | # Now start the search over again on the new object with more verts 71 | snapped_verts = {} 72 | for vid, v in enumerate(verts): 73 | for gvid, gv in enumerate(geo_verts): 74 | mag = (v-gv).magnitude 75 | if mag < scn.tol: 76 | snapped_verts[vid] = gvid 77 | break # We have found a vertex co-located, continue with next block vertex 78 | 79 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(True,False,False)") 80 | for edid, ed in enumerate(edges): 81 | if ed[0] in snapped_verts and ed[1] in snapped_verts and not nosnap[edid]: 82 | geoobj.hide = False 83 | bpy.ops.object.mode_set(mode='EDIT') 84 | bpy.ops.mesh.select_all(action='DESELECT') 85 | bpy.ops.object.mode_set(mode='OBJECT') 86 | geoobj.data.vertices[snapped_verts[ed[0]]].select = True 87 | geoobj.data.vertices[snapped_verts[ed[1]]].select = True 88 | bpy.ops.object.mode_set(mode='EDIT') 89 | try: 90 | bpy.ops.mesh.select_vertex_path(type='EDGE_LENGTH') 91 | except: 92 | bpy.ops.mesh.shortest_path_select(use_length=True) 93 | bpy.ops.object.mode_set(mode='OBJECT') 94 | bpy.ops.object.mode_set(mode='EDIT') 95 | bpy.ops.mesh.duplicate() 96 | bpy.ops.object.mode_set(mode='OBJECT') 97 | bpy.ops.object.mode_set(mode='EDIT') 98 | bpy.ops.mesh.separate(type='SELECTED') 99 | bpy.ops.object.mode_set(mode='OBJECT') 100 | polyLineobj = bpy.data.objects['deleteme.001'] 101 | if len(polyLineobj.data.vertices) > 2: 102 | polyLineverts = list(blender_utils.vertices_from_mesh(polyLineobj)) 103 | polyLineedges = list(blender_utils.edges_from_mesh(polyLineobj)) 104 | for vid, v in enumerate(polyLineverts): 105 | mag = (v-verts[ed[0]]).magnitude 106 | if mag < scn.tol: 107 | startVertex = vid 108 | break 109 | polyLineStr, vectors, length = sortedVertices(polyLineverts,polyLineedges,startVertex) 110 | polyLinesPoints.append([ed[0],ed[1],vectors]) 111 | polyLinesLengths[0].append([min(ed[0],ed[1]), max(ed[0],ed[1])]) # write out sorted 112 | polyLinesLengths[1].append(length) 113 | polyLine = 'polyLine {} {} ('.format(*ed) 114 | polyLine += polyLineStr 115 | polyLine += ')\n' 116 | polyLines += polyLine 117 | 118 | geoobj.select = False 119 | polyLineobj.select = True 120 | bpy.ops.object.delete() 121 | geoobj.select = True 122 | bpy.ops.object.delete() 123 | return polyLines, polyLinesPoints, polyLinesLengths 124 | 125 | def sortedVertices(verts,edges,startVert): 126 | sorted = [] 127 | vectors = [] 128 | sorted.append(startVert) 129 | vert = startVert 130 | length = len(edges)+1 131 | for i in range(len(verts)): 132 | for eid, e in enumerate(edges): 133 | if vert in e: 134 | if e[0] == vert: 135 | sorted.append(e[1]) 136 | else: 137 | sorted.append(e[0]) 138 | edges.pop(eid) 139 | vert = sorted[-1] 140 | break 141 | 142 | polyLine = '' 143 | length = 0. 144 | for vid, v in enumerate(sorted): 145 | polyLine += '({} {} {})'.format(*verts[v]) 146 | vectors.append(verts[v]) 147 | if vid>=1: 148 | length += (vectors[vid] - vectors[vid-1]).magnitude 149 | return polyLine, vectors, length 150 | 151 | def patchColor(patch_no): 152 | color = [(1.0,0.,0.), (0.0,1.,0.),(0.0,0.,1.),(0.707,0.707,0),(0,0.707,0.707),(0.707,0,0.707)] 153 | return color[patch_no % len(color)] 154 | 155 | def initProperties(): 156 | 157 | bpy.types.Scene.tol = FloatProperty( 158 | name = "tol", 159 | description = "Snapping tolerance", 160 | default = 1e-6, 161 | min = 0.) 162 | 163 | bpy.types.Scene.ctmFloat = FloatProperty( 164 | name = "convertToMeters", 165 | description = "Conversion factor: Blender coords to meter", 166 | default = 1.0, 167 | min = 0.) 168 | 169 | bpy.types.Scene.resFloat = FloatProperty( 170 | name = "Resolution", 171 | description = "The average spatial resolution of generated mesh in meter", 172 | default = 1.0, 173 | min = 0) 174 | 175 | bpy.types.Scene.resForce = IntProperty( 176 | name = "# cells", 177 | description = "For forcing the number of cells on edge (0 disables)", 178 | default = 0, 179 | min = 0) 180 | 181 | bpy.types.Scene.grading = FloatProperty( 182 | name = "Grading", 183 | description = "The size ratio of first and last cell on edge", 184 | default = 1.0) 185 | 186 | bpy.types.Scene.whichCell = EnumProperty( 187 | items = [('Coarse', 'Coarse', 'Let the coarse cells have the target resolution'), 188 | ('Fine', 'Fine', 'Let the fine cells have the target resolution') 189 | ], 190 | name = "Cell resolution") 191 | 192 | bpy.types.Scene.setEdges = BoolProperty( 193 | name = "Set edges", 194 | description = "Should edges be fetched from another object?", 195 | default = False) 196 | 197 | bpy.types.Scene.geoobjName = StringProperty( 198 | name = "Object", 199 | description = "Name of object to get edges from (this box disappears when object is found)", 200 | default = '') 201 | 202 | bpy.types.Scene.bcTypeEnum = EnumProperty( 203 | items = [('wall', 'wall', 'Defines the patch as wall'), 204 | ('patch', 'patch', 'Defines the patch as generic patch'), 205 | ('empty', 'empty', 'Defines the patch as empty'), 206 | ('symmetryPlane', 'symmetryPlane', 'Defines the patch as symmetryPlane'), 207 | ], 208 | name = "Patch type") 209 | 210 | bpy.types.Scene.patchName = StringProperty( 211 | name = "Patch name", 212 | description = "Specify name of patch (max 31 chars)", 213 | default = "defaultName") 214 | 215 | bpy.types.Scene.snapping = EnumProperty( 216 | items = [('yes', 'Yes', 'The edge gets a polyLine if its vertices are snapped'), 217 | ('no', 'No', 'The edge will be meshed as a straight line') 218 | ], 219 | name = "Edge snapping") 220 | 221 | bpy.types.Scene.removeInternal = BoolProperty( 222 | name = "Remove internal faces", 223 | description = "Should internal faces be removed?", 224 | default = False) 225 | 226 | bpy.types.Scene.createBoundary = BoolProperty( 227 | name = "Create boundary faces", 228 | description = "Should boundary faces be created?", 229 | default = False) 230 | 231 | return 232 | 233 | # 234 | # Menu in UI region 235 | # 236 | class UIPanel(bpy.types.Panel): 237 | bl_label = "SwiftBlock settings" 238 | bl_space_type = 'PROPERTIES' 239 | bl_region_type = 'WINDOW' 240 | bl_context = "object" 241 | 242 | def draw(self, context): 243 | layout = self.layout 244 | scn = context.scene 245 | obj = context.active_object 246 | settings = context.tool_settings 247 | try: 248 | obj['swiftblock'] 249 | except: 250 | try: 251 | obj['swiftBlockObj'] 252 | layout.operator("delete.preview") 253 | except: 254 | layout.operator("enable.swiftblock") 255 | else: 256 | layout = layout.column() 257 | layout.operator("write.bmdfile") 258 | layout.operator("create.preview") 259 | layout.operator("find.broken") 260 | layout.prop(scn, 'ctmFloat') 261 | layout.prop(scn, 'resFloat') 262 | box = layout.box() 263 | box = box.column() 264 | box.label(text='Edge settings') 265 | box.prop(scn, 'setEdges') 266 | if scn.setEdges: 267 | try: 268 | geoojb = bpy.data.objects[scn.geoobjName] 269 | textstr = "Fetching egde's polyLines from " + geoojb.name 270 | box.operator("change.geoobj", text=textstr, emboss=False) 271 | # box.prop(scn, 'tol') # the tolerance setting do not behave as I expected... do not adjust for now 272 | split = box.split() 273 | col = split.column() 274 | col.operator('nosnap.edge', text='Curved').snapping = True 275 | col = split.column() 276 | col.operator('nosnap.edge', text='Straight') 277 | box.separator() 278 | except: 279 | box.prop(scn, 'geoobjName') 280 | 281 | split = box.split() 282 | col = split.column() 283 | col.prop(scn, 'resForce') 284 | col.prop(scn, 'grading') 285 | col.operator("flip.edge") 286 | col = split.column() 287 | col.operator("set.edgeres") 288 | col.row().prop(scn,"whichCell", expand=True) 289 | col.operator("set.grading") 290 | col.operator("show.edgeinfo", text="Edge settings") 291 | 292 | box = layout.box() 293 | box = box.column() 294 | box.label(text='Patch settings') 295 | box.prop(scn, 'patchName') 296 | box.prop(scn, 'bcTypeEnum') 297 | box.operator("set.patchname") 298 | for m in obj.data.materials: 299 | try: 300 | patchtype = str(' ' + m['patchtype']) 301 | split = box.split(percentage=0.2, align=True) 302 | col = split.column() 303 | col.prop(m, "diffuse_color", text="") 304 | col = split.column() 305 | col.operator("set.getpatch", text=m.name + patchtype, emboss=False).whichPatch = m.name 306 | except: 307 | pass 308 | box.operator("repair.faces") 309 | 310 | group = obj.vertex_groups.active 311 | rows = 2 312 | if group: 313 | rows = 4 314 | 315 | layout.label('Block\'s name settings') 316 | row = layout.row() 317 | row.template_list("MESH_UL_vgroups", "", obj, "vertex_groups", obj.vertex_groups, "active_index", rows=rows) 318 | col = row.column(align=True) 319 | col.operator("object.vertex_group_add", icon='ZOOMIN', text="") 320 | col.operator("object.vertex_group_remove", icon='ZOOMOUT', text="").all = False 321 | if group: 322 | col.separator() 323 | col.operator("object.vertex_group_move", icon='TRIA_UP', text="").direction = 'UP' 324 | col.operator("object.vertex_group_move", icon='TRIA_DOWN', text="").direction = 'DOWN' 325 | 326 | if group: 327 | row = layout.row() 328 | row.prop(group, "name") 329 | 330 | if obj.vertex_groups and obj.mode == 'EDIT': 331 | row = layout.row() 332 | 333 | sub = row.row(align=True) 334 | sub.operator("object.vertex_group_assign", text="Assign") 335 | sub.operator("object.vertex_group_remove_from", text="Remove") 336 | sub = row.row(align=True) 337 | sub.operator("object.vertex_group_select", text="Select") 338 | sub.operator("object.vertex_group_deselect", text="Deselect") 339 | 340 | class OBJECT_OT_edgeInfo(bpy.types.Operator): 341 | '''Show/edit settings for one selected edge''' 342 | bl_idname = "show.edgeinfo" 343 | bl_label = "Show and edit settings for selected edge" 344 | 345 | def execute(self, context): 346 | bpy.ops.object.mode_set(mode='OBJECT') 347 | obj = context.active_object 348 | scn = context.scene 349 | for e in obj.data.edges: 350 | if e.select: 351 | if scn.snapping == 'yes': 352 | e.use_edge_sharp = False 353 | else: 354 | e.use_edge_sharp = True 355 | 356 | bpy.ops.set.edgeres() 357 | bpy.ops.set.grading() 358 | bpy.ops.object.mode_set(mode='EDIT') 359 | return {'FINISHED'} 360 | 361 | def invoke(self, context, event): 362 | wm = context.window_manager 363 | obj = context.active_object 364 | scn = context.scene 365 | bpy.ops.object.mode_set(mode='OBJECT') 366 | bpy.ops.object.mode_set(mode='EDIT') 367 | NoSelected = 0 368 | for e in obj.data.edges: 369 | if e.select: 370 | NoSelected += 1 371 | if e.use_seam: 372 | scn.whichCell = 'Fine' 373 | else: 374 | scn.whichCell = 'Coarse' 375 | 376 | if e.bevel_weight >= 0.001: 377 | scn.resForce = obj['bevelToResMap'][str(round(e.bevel_weight*100))] 378 | else: 379 | scn.resForce = 0 380 | 381 | if e.crease == 0: 382 | scn.grading = 1 383 | else: 384 | scn.grading = obj['creaseToGradMap'][str(round(e.crease*100))] 385 | 386 | if e.use_edge_sharp: 387 | scn.snapping = 'no' 388 | else: 389 | scn.snapping = 'yes' 390 | if NoSelected >= 2: 391 | self.report({'INFO'}, "More than one edge selected!") 392 | return{'CANCELLED'} 393 | elif NoSelected == 0: 394 | self.report({'INFO'}, "Please select an edge!") 395 | return{'CANCELLED'} 396 | context.window_manager.invoke_props_dialog(self, width=400) 397 | return {'RUNNING_MODAL'} 398 | 399 | def draw(self, context): 400 | scn = context.scene 401 | split = self.layout.split(percentage=0.5) 402 | col = split.column() 403 | col.label("Define polyLine:") 404 | col.label("Grading:") 405 | col.label("Which cell gets target res:") 406 | col.label("Forced resolution:") 407 | col = split.column() 408 | 409 | col.row().prop(scn, "snapping", expand=True) 410 | col.prop(scn, "grading") 411 | col.row().prop(scn,"whichCell", expand=True) 412 | col.prop(scn, "resForce") 413 | 414 | class OBJECT_OT_nosnapEdge(bpy.types.Operator): 415 | '''Force selected edge(s) straight or curved''' 416 | bl_idname = "nosnap.edge" 417 | bl_label = "No snap" 418 | 419 | snapping = BoolProperty(default = False) 420 | 421 | def invoke(self, context, event): 422 | bpy.ops.object.mode_set(mode='OBJECT') 423 | obj = context.active_object 424 | NoSelect = 0 425 | for e in obj.data.edges: 426 | if e.select: 427 | NoSelect += 1 428 | if not self.snapping: 429 | e.use_edge_sharp = True 430 | else: 431 | e.use_edge_sharp = False 432 | 433 | if not NoSelect: 434 | self.report({'INFO'}, "No edge(s) selected!") 435 | bpy.ops.object.mode_set(mode='EDIT') 436 | return{'CANCELLED'} 437 | 438 | bpy.ops.object.mode_set(mode='EDIT') 439 | return {'RUNNING_MODAL'} 440 | 441 | 442 | class OBJECT_OT_insertSmoother(bpy.types.Operator): 443 | '''Inserts a smoother''' 444 | bl_idname = "insert.smoother" 445 | bl_label = "Insert smoother" 446 | 447 | def execute(self, context): 448 | try: 449 | bpy.data.objects[context.scene.geoobjName] 450 | except: 451 | self.report({'INFO'}, "Cannot find object for edges!") 452 | return{'CANCELLED'} 453 | import mathutils 454 | from . import utils 455 | bpy.ops.object.mode_set(mode='OBJECT') 456 | scn = context.scene 457 | obj = context.active_object 458 | obj.select=False 459 | geoobj = bpy.data.objects[scn.geoobjName] 460 | geoobj.hide = False 461 | centre = mathutils.Vector((0,0,0)) 462 | no_verts = 0 463 | profile = utils.smootherProfile() 464 | res = profile.__len__() 465 | matrix = obj.matrix_world.copy() 466 | for v in obj.data.vertices: 467 | if v.select: 468 | centre += matrix*v.co 469 | no_verts += 1 470 | if no_verts == 0: 471 | self.report({'INFO'}, "Nothing selected!") 472 | return{'CANCELLED'} 473 | 474 | centre /= no_verts 475 | if no_verts <= 2: 476 | centre = mathutils.Vector((0,0,centre[2])) 477 | for e in obj.data.edges: 478 | if e.select: 479 | (v0id, v1id) = e.vertices 480 | v0 = matrix*obj.data.vertices[v0id].co 481 | v1 = matrix*obj.data.vertices[v1id].co 482 | edgevector = v1-v0 483 | normal = centre-v0 484 | tang = normal.project(edgevector) 485 | normal -= tang 486 | normal.normalize() 487 | normal *= -edgevector.length 488 | p = [v0 for i in range(res)] 489 | e = [[0,1] for i in range(res)] 490 | for i in range(res): 491 | linecoord = float(i)/(res-1) 492 | p[i] = (1-linecoord)*v0+linecoord*v1 493 | p[i] += 0.05*normal*profile[i] 494 | for i in range(res-1): 495 | e[i] = [i,i+1] 496 | mesh_data = bpy.data.meshes.new("deleteme") 497 | mesh_data.from_pydata(p, e, []) 498 | mesh_data.update() 499 | addtoobj = bpy.data.objects.new('deleteme', mesh_data) 500 | bpy.context.scene.objects.link(addtoobj) 501 | bpy.data.objects['deleteme'].select = True 502 | geoobj.select = True 503 | scn.objects.active = geoobj 504 | bpy.ops.object.join() 505 | geoobj.select = False 506 | 507 | geoobj.select = True 508 | scn.objects.active = geoobj 509 | bpy.ops.object.mode_set(mode='EDIT') 510 | bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=False) 511 | bpy.ops.object.mode_set(mode='OBJECT') 512 | geoobj.select = False 513 | obj.select = True 514 | scn.objects.active = obj 515 | bpy.ops.object.mode_set(mode='EDIT') 516 | return {'FINISHED'} 517 | 518 | class OBJECT_OT_flipEdge(bpy.types.Operator): 519 | '''Flip direction of selected edge(s). This is useful if you want to \ 520 | set grading on several edges which are initially misaligned''' 521 | bl_idname = "flip.edge" 522 | bl_label = "Flip edge" 523 | 524 | def execute(self, context): 525 | bpy.ops.object.mode_set(mode='OBJECT') 526 | obj = context.active_object 527 | for e in obj.data.edges: 528 | if e.select: 529 | (e0, e1) = e.vertices 530 | e.vertices = (e1, e0) 531 | 532 | bpy.ops.object.mode_set(mode='EDIT') 533 | return {'FINISHED'} 534 | 535 | class OBJECT_OT_deletePreview(bpy.types.Operator): 536 | '''Delete preview mesh object''' 537 | bl_idname = "delete.preview" 538 | bl_label = "Delete preview mesh" 539 | 540 | def execute(self, context): 541 | bpy.ops.object.mode_set(mode='OBJECT') 542 | name = '' 543 | for obj in bpy.data.objects: 544 | try: 545 | name = obj['swiftBlockObj'] 546 | except: 547 | obj.select = False 548 | bpy.ops.object.delete() 549 | try: 550 | obj = bpy.data.objects[name] 551 | obj.select = True 552 | obj.hide = False 553 | bpy.context.scene.objects.active = obj 554 | except: 555 | pass 556 | return {'FINISHED'} 557 | 558 | class OBJECT_OT_ChangeGeoObj(bpy.types.Operator): 559 | '''Click to change object''' 560 | bl_idname = "change.geoobj" 561 | bl_label = "Change" 562 | 563 | def execute(self, context): 564 | context.scene.geoobjName = '' 565 | return {'FINISHED'} 566 | 567 | class OBJECT_OT_Enable(bpy.types.Operator): 568 | '''Enables SwiftBlock for the active object''' 569 | bl_idname = "enable.swiftblock" 570 | bl_label = "Enable SwiftBlock" 571 | 572 | def execute(self, context): 573 | obj = context.active_object 574 | obj['swiftblock'] = True 575 | bpy.context.tool_settings.use_mesh_automerge = True 576 | 577 | bpy.ops.object.mode_set(mode='OBJECT') 578 | obj.data.use_customdata_edge_crease = True 579 | obj.data.use_customdata_edge_bevel = True 580 | bpy.ops.object.material_slot_remove() 581 | bpy.ops.object.mode_set(mode='EDIT') 582 | bpy.ops.mesh.select_all(action='DESELECT') 583 | bpy.ops.object.mode_set(mode='OBJECT') 584 | try: 585 | mat = bpy.data.materials['defaultName'] 586 | patchindex = list(obj.data.materials).index(mat) 587 | obj.active_material_index = patchindex 588 | except: 589 | mat = bpy.data.materials.new('defaultName') 590 | mat.diffuse_color = (0.5,0.5,0.5) 591 | bpy.ops.object.material_slot_add() 592 | obj.material_slots[-1].material = mat 593 | mat['patchtype'] = 'wall' 594 | bpy.ops.object.editmode_toggle() 595 | bpy.ops.object.material_slot_assign() 596 | bpy.ops.mesh.select_all(action='DESELECT') 597 | bpy.ops.object.editmode_toggle() 598 | return{'FINISHED'} 599 | 600 | class OBJECT_OT_SetPatchName(bpy.types.Operator): 601 | '''Set the given name to the selected faces''' 602 | bl_idname = "set.patchname" 603 | bl_label = "Set name" 604 | 605 | def execute(self, context): 606 | scn = context.scene 607 | obj = context.active_object 608 | bpy.ops.object.mode_set(mode='OBJECT') 609 | NoSelected = 0 610 | for f in obj.data.polygons: 611 | if f.select: 612 | NoSelected += 1 613 | if NoSelected: 614 | namestr = scn.patchName 615 | namestr = namestr.strip() 616 | namestr = namestr.replace(' ', '_') 617 | try: 618 | mat = bpy.data.materials[namestr] 619 | patchindex = list(obj.data.materials).index(mat) 620 | obj.active_material_index = patchindex 621 | except: # add a new patchname (as a blender material, as such face props are conserved during mesh mods) 622 | mat = bpy.data.materials.new(namestr) 623 | mat.diffuse_color = patchColor(len(obj.data.materials)-1) 624 | bpy.ops.object.material_slot_add() 625 | obj.material_slots[-1].material = mat 626 | mat['patchtype'] = scn.bcTypeEnum 627 | bpy.ops.object.editmode_toggle() 628 | bpy.ops.object.material_slot_assign() 629 | else: 630 | self.report({'INFO'}, "No faces selected!") 631 | return{'CANCELLED'} 632 | return {'FINISHED'} 633 | 634 | class OBJECT_OT_SetEdgeRes(bpy.types.Operator): 635 | '''Force a resolution on selected edge(s)''' 636 | bl_idname = "set.edgeres" 637 | bl_label = "Force resolution" 638 | # This very messy way to keep track of resolution is needed as we have to store the info 639 | # in edges native properties. Here bevel_weight is used which is a float in range [0,1] 640 | # The float can store approx. 100 different values. By mult. by 100, int in range [0,100] 641 | # is achieved. This int is mapped to user-set resolution with the 'bevelToResMap' 642 | 643 | def execute(self, context): 644 | scn = context.scene 645 | obj = context.active_object 646 | res = scn.resForce 647 | bpy.ops.object.mode_set(mode='OBJECT') 648 | NoSelect = 0 649 | for e in obj.data.edges: 650 | if e.select == True: 651 | NoSelect += 1 652 | if not NoSelect: 653 | self.report({'INFO'}, "No edge(s) selected!") 654 | bpy.ops.object.mode_set(mode='EDIT') 655 | return{'CANCELLED'} 656 | 657 | try: 658 | obj['bevelToResMap'] 659 | except: 660 | obj['bevelToResMap']= {} 661 | 662 | existingRes = set() 663 | for e in obj.data.edges: 664 | if str(round(e.bevel_weight*100)) in obj['bevelToResMap']: 665 | existingRes.add(round(e.bevel_weight*100)) 666 | else: 667 | e.bevel_weight = 0 #remove bevel if no entry in bevelToResMap was found 668 | 669 | mapEntryToRemove = set() 670 | for entry in obj['bevelToResMap']: 671 | if not int(entry) in existingRes: 672 | mapEntryToRemove.add(entry) 673 | 674 | for entry in mapEntryToRemove: 675 | obj['bevelToResMap'].pop(entry) 676 | 677 | if res == 0: 678 | for e in obj.data.edges: 679 | if e.select == True: 680 | e.bevel_weight = 0 681 | bpy.ops.object.mode_set(mode='EDIT') 682 | return {'FINISHED'} 683 | 684 | bevelToResMap = obj['bevelToResMap'] 685 | foundRes = False 686 | for bevelInt in bevelToResMap: 687 | if bevelToResMap[bevelInt] == res: # previously used resolution - reuse! 688 | bevel = int(bevelInt) 689 | foundRes = True 690 | if not foundRes: 691 | allEntries = set(range(1,101)) #all possible bevel entries 692 | newEntry = min(list(allEntries.difference(existingRes))) 693 | bevelToResMap[str(newEntry)] = res # create a new entry in map 694 | bevel = newEntry 695 | for e in obj.data.edges: 696 | if e.select == True: 697 | e.bevel_weight = bevel/100. 698 | bpy.ops.object.mode_set(mode='EDIT') 699 | return {'FINISHED'} 700 | 701 | 702 | class OBJECT_OT_SetGrading(bpy.types.Operator): 703 | '''Set grading on selected edge(s). Use Ctrl-Alt-Space to find out orientation of each edge. \ 704 | Cells will be coarser in the edge's z-direction for grading > 1''' 705 | bl_idname = "set.grading" 706 | bl_label = "Set grading" 707 | # This very messy way to keep track of grading is needed as we have to store the info 708 | # in edges native properties. Here crease is used which is a float in range [0,1] 709 | # The float can store approx. 100 different values. By mult. by 100, int in range [0,100] 710 | # is achieved. This int is mapped to user-set grading with the 'creaseToGradMap' 711 | 712 | def execute(self, context): 713 | scn = context.scene 714 | obj = context.active_object 715 | grad = scn.grading 716 | if scn.whichCell == 'Fine': 717 | use_seam = True 718 | else: 719 | use_seam = False 720 | 721 | bpy.ops.object.mode_set(mode='OBJECT') 722 | NoSelect = 0 723 | for e in obj.data.edges: 724 | if e.select == True: 725 | NoSelect += 1 726 | if not NoSelect: 727 | self.report({'INFO'}, "No edge(s) selected!") 728 | bpy.ops.object.mode_set(mode='EDIT') 729 | return{'CANCELLED'} 730 | 731 | try: 732 | obj['creaseToGradMap'] 733 | except: 734 | obj['creaseToGradMap']= {} 735 | obj.data.show_edge_crease = False # could be set true to show which edges have grading 736 | if grad == 1: 737 | for e in obj.data.edges: 738 | if e.select == True: 739 | e.crease = 0 740 | bpy.ops.object.mode_set(mode='EDIT') 741 | return {'FINISHED'} 742 | 743 | existingGrad = set() 744 | for e in obj.data.edges: 745 | if str(round(e.crease*100)) in obj['creaseToGradMap']: 746 | existingGrad.add(round(e.crease*100)) 747 | else: 748 | e.crease = 0 #remove bevel if no entry in bevelToResMap was found 749 | 750 | mapEntryToRemove = set() 751 | for entry in obj['creaseToGradMap']: 752 | if not int(entry) in existingGrad: 753 | mapEntryToRemove.add(entry) 754 | 755 | for entry in mapEntryToRemove: 756 | obj['creaseToGradMap'].pop(entry) 757 | 758 | creaseToGradMap = obj['creaseToGradMap'] 759 | foundGrad = False 760 | for creaseInt in creaseToGradMap: 761 | if creaseToGradMap[creaseInt] == grad: # previously used grading - reuse! 762 | crease = int(creaseInt) 763 | foundGrad = True 764 | if not foundGrad: 765 | allEntries = set(range(1,101)) #all possible bevel entries 766 | newEntry = min(list(allEntries.difference(existingGrad))) 767 | creaseToGradMap[str(newEntry)] = grad # create a new entry in map 768 | crease = newEntry 769 | for e in obj.data.edges: 770 | if e.select == True: 771 | e.crease = crease/100. 772 | e.use_seam = use_seam 773 | bpy.ops.object.mode_set(mode='EDIT') 774 | bpy.ops.object.mode_set(mode='OBJECT') 775 | bpy.ops.object.mode_set(mode='EDIT') 776 | return {'FINISHED'} 777 | 778 | 779 | class OBJECT_OT_FindBroken(bpy.types.Operator): 780 | '''Detect blocks and mark unused edges''' 781 | bl_idname = "find.broken" 782 | bl_label = "Diagnose" 783 | 784 | def execute(self, context): 785 | from . import utils 786 | import imp 787 | imp.reload(utils) 788 | from . import blender_utils 789 | bpy.ops.object.mode_set(mode='OBJECT') 790 | bpy.ops.object.mode_set(mode='EDIT') 791 | bpy.ops.mesh.select_all(action='DESELECT') 792 | bpy.ops.object.mode_set(mode='OBJECT') 793 | obj = context.active_object 794 | verts = list(blender_utils.vertices_from_mesh(obj)) 795 | edges = list(blender_utils.edges_from_mesh(obj)) 796 | refEdges = list(blender_utils.edges_from_mesh(obj)) 797 | 798 | log, block_print_out, dependent_edges, face_info, all_edges, faces_as_list_of_nodes = utils.blockFinder(edges, verts, '','', []) 799 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(False,True,False)") 800 | for e in obj.data.edges: 801 | e.select = True 802 | 803 | def edgeFinder(v0, v1, edgeList): 804 | if [v0, v1] in edgeList: 805 | return edgeList.index([v0, v1]) 806 | if [v1, v0] in edgeList: 807 | return edgeList.index([v1, v0]) 808 | return -1 809 | 810 | edgeOrder = [[0,1], [1,2], [2,3], [0,3], [4,5], [5,6], [6,7], [4,7], [0,4], [1,5], [2,6], [3,7]] 811 | for vl in block_print_out: 812 | for e in edgeOrder: 813 | v0 = vl[e[0]] 814 | v1 = vl[e[1]] 815 | obj.data.edges[edgeFinder(v0, v1, refEdges)].select = False 816 | 817 | bpy.ops.object.mode_set(mode='EDIT') 818 | return {'FINISHED'} 819 | 820 | 821 | class OBJECT_OT_RepairFaces(bpy.types.Operator): 822 | '''Delete internal face and create boundary faces''' 823 | bl_idname = "repair.faces" 824 | bl_label = "Repair" 825 | 826 | c = EnumProperty( 827 | items = [('wall', 'wall', 'Defines the patch as wall'), 828 | ('patch', 'patch', 'Defines the patch as generic patch'), 829 | ('empty', 'empty', 'Defines the patch as empty'), 830 | ('symmetryPlane', 'symmetryPlane', 'Defines the patch as symmetryPlane'), 831 | ], 832 | name = "Patch type") 833 | 834 | def execute(self, context): 835 | from . import utils 836 | import imp 837 | imp.reload(utils) 838 | from . import blender_utils 839 | 840 | removeInternal = bpy.context.scene.removeInternal 841 | createBoundary = bpy.context.scene.createBoundary 842 | 843 | if not createBoundary and not removeInternal: 844 | self.report({'INFO'}, "Repair: Nothing to do!") 845 | return{'CANCELLED'} 846 | 847 | bpy.ops.object.mode_set(mode='OBJECT') 848 | bpy.ops.object.mode_set(mode='EDIT') 849 | bpy.ops.mesh.select_all(action='DESELECT') 850 | bpy.ops.object.mode_set(mode='OBJECT') 851 | obj = context.active_object 852 | verts = list(blender_utils.vertices_from_mesh(obj)) 853 | edges = list(blender_utils.edges_from_mesh(obj)) 854 | 855 | disabled = [] 856 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(True,False,False)") 857 | for group in obj.vertex_groups: 858 | bpy.ops.object.mode_set(mode='EDIT') 859 | bpy.ops.mesh.select_all(action='DESELECT') 860 | if group.name == 'disabled': 861 | bpy.ops.object.vertex_group_set_active(group=group.name) 862 | bpy.ops.object.vertex_group_select() 863 | bpy.ops.object.mode_set(mode='OBJECT') 864 | vlist = [] 865 | for v in obj.data.vertices: 866 | if v.select == True: 867 | disabled += [v.index] 868 | 869 | bpy.ops.object.mode_set(mode='EDIT') 870 | bpy.ops.mesh.select_all(action='DESELECT') 871 | bpy.ops.object.mode_set(mode='OBJECT') 872 | nRemoved, nCreated = utils.repairFaces(edges, verts, disabled, obj, removeInternal, createBoundary) 873 | self.report({'INFO'}, "Created {} boundary faces and removed {} internal faces".format(nCreated, nRemoved)) 874 | return {'FINISHED'} 875 | 876 | def invoke(self, context, event): 877 | context.window_manager.invoke_props_dialog(self, width=200) 878 | return {'RUNNING_MODAL'} 879 | 880 | def draw(self, context): 881 | scn = context.scene 882 | obj = context.object 883 | nPatches = obj.material_slots.__len__() 884 | self.layout.prop(scn, "removeInternal") 885 | self.layout.prop(scn, "createBoundary") 886 | self.layout.label("Assign new faces to patch") 887 | self.layout.template_list("MATERIAL_UL_matslots", "", obj, "material_slots", obj, "active_material_index", rows=nPatches) 888 | 889 | 890 | class OBJECT_OT_GetPatch(bpy.types.Operator): 891 | '''Click to select faces belonging to this patch''' 892 | bl_idname = "set.getpatch" 893 | bl_label = "Get patch" 894 | 895 | whichPatch = StringProperty() 896 | 897 | def execute(self, context): 898 | scn = context.scene 899 | obj = context.active_object 900 | bpy.ops.object.mode_set(mode='OBJECT') 901 | bpy.ops.object.mode_set(mode='EDIT') 902 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(False,False,True)") 903 | bpy.ops.mesh.select_all(action='DESELECT') 904 | bpy.ops.object.mode_set(mode='OBJECT') 905 | mat = bpy.data.materials[self.whichPatch] 906 | patchindex = list(obj.data.materials).index(mat) 907 | obj.active_material_index = patchindex 908 | bpy.ops.object.mode_set(mode='EDIT') 909 | bpy.ops.object.material_slot_select() 910 | scn.bcTypeEnum = mat['patchtype'] 911 | scn.patchName = self.whichPatch 912 | return {'FINISHED'} 913 | 914 | class OBJECT_OT_createPreview(bpy.types.Operator): 915 | '''Creates a mesh preview as a separate object from selected vertices''' 916 | bl_idname = "create.preview" 917 | bl_label = "Preview" 918 | 919 | def execute(self, context): 920 | if context.scene.setEdges: 921 | try: 922 | bpy.data.objects[context.scene.geoobjName] 923 | except: 924 | self.report({'INFO'}, "Cannot find object for edges!") 925 | return{'CANCELLED'} 926 | import locale 927 | from . import utils 928 | import imp, math 929 | imp.reload(utils) 930 | from . import blender_utils 931 | locale.setlocale(locale.LC_ALL, '') 932 | scn = context.scene 933 | obj = context.active_object 934 | 935 | bpy.ops.object.mode_set(mode='OBJECT') #update 936 | bpy.ops.object.mode_set(mode='EDIT') 937 | 938 | 939 | verts = list(blender_utils.vertices_from_mesh(obj)) 940 | edges = list(blender_utils.edges_from_mesh(obj)) 941 | 942 | toShow = [0 for i in range(len(verts))] # A block is previewed if all vertices are selected 943 | allOff = True 944 | for vid in range(len(verts)): 945 | if obj.data.vertices[vid].select: 946 | toShow[vid] = 1 947 | allOff = False 948 | if allOff: # All vertices were unselected - proceed with previewing all blocks 949 | toShow = [1 for i in range(len(verts))] 950 | 951 | disabled = [] 952 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(True,False,False)") 953 | for group in obj.vertex_groups: 954 | bpy.ops.object.mode_set(mode='EDIT') 955 | bpy.ops.mesh.select_all(action='DESELECT') 956 | if group.name == 'disabled': 957 | bpy.ops.object.vertex_group_set_active(group=group.name) 958 | bpy.ops.object.vertex_group_select() 959 | bpy.ops.object.mode_set(mode='OBJECT') 960 | vlist = [] 961 | for v in obj.data.vertices: 962 | if v.select == True: 963 | disabled += [v.index] 964 | 965 | bpy.ops.object.mode_set(mode='EDIT') 966 | bpy.ops.mesh.select_all(action='DESELECT') 967 | bpy.ops.object.mode_set(mode='OBJECT') 968 | 969 | if not allOff: 970 | for vid, v in enumerate(obj.data.vertices): #restore selection 971 | if toShow[vid]: 972 | v.select = True 973 | 974 | forcedEdges = [] 975 | for e in obj.data.edges: 976 | if e.bevel_weight >= 0.001: 977 | N = obj['bevelToResMap'][str(round(e.bevel_weight*100))] 978 | forcedEdges.append([[e.vertices[0],e.vertices[1]], N]) 979 | 980 | gradedEdges = [] 981 | for e in obj.data.edges: 982 | if e.crease == 0: 983 | grad = 1 984 | else: 985 | grad = obj['creaseToGradMap'][str(round(e.crease*100))] 986 | gradedEdges.append([[e.vertices[0],e.vertices[1]], grad, e.use_seam]) 987 | 988 | bpy.ops.object.mode_set(mode='OBJECT') 989 | obj.select = False 990 | if scn.setEdges: 991 | polyLines, polyLinesPoints, lengths = getPolyLines(verts, edges, obj) 992 | else: 993 | polyLinesPoints = [] 994 | lengths = [[], []] 995 | 996 | dx0 = scn.resFloat 997 | effective_lengths = [[], []] 998 | for e in edges: 999 | if e in lengths[0]: 1000 | ind = lengths[0].index(e) 1001 | length = lengths[1][ind] 1002 | elif [e[0],e[1]] in lengths[0]: 1003 | ind = lengths[0].index([e[0],e[1]]) 1004 | length = lengths[1][ind] 1005 | else: 1006 | length = (verts[e[0]] - verts[e[1]]).magnitude 1007 | 1008 | if length < 1e-6: 1009 | self.report({'INFO'}, "Zero length edge detected, check block structure!") 1010 | return{'FINISHED'} 1011 | 1012 | for ge in gradedEdges: 1013 | if ge[0] == e or ge[0] == [e[0],e[1]]: 1014 | grad = ge[1] 1015 | fine = ge[2] 1016 | break 1017 | if fine: #Fine cells should match the target resolution, this means fewer total cells 1018 | if grad > 1.00001: 1019 | if grad*dx0/length < 1: 1020 | length *= (math.log(grad)/(math.log(1-dx0/length)-math.log(1-grad*dx0/length)) +1) * dx0/length 1021 | elif grad < 0.99999: 1022 | if dx0/length < 1: 1023 | length /= (math.log(grad)/(math.log(1-dx0/length)-math.log(1-grad*dx0/length)) +1) * dx0/length 1024 | else: 1025 | pass 1026 | else: #Coarse cells should match the target resolution, this means more total cells 1027 | if grad > 1.00001: 1028 | if dx0/length < 1: 1029 | length *= (math.log(grad)/(math.log(1-dx0/(length*grad))-math.log(1-dx0/length)) +1) * dx0/length 1030 | elif grad < 0.99999: 1031 | if dx0/(length*grad) < 1: 1032 | length /= (math.log(grad)/(math.log(1-dx0/(length*grad))-math.log(1-dx0/length)) +1) * dx0/length 1033 | else: 1034 | pass 1035 | # Now we have a final effective edge length. Lets store! 1036 | effective_lengths[0].append([min(e[0],e[1]), max(e[0],e[1])]) # write out sorted 1037 | effective_lengths[1].append(length) 1038 | 1039 | size, NoCells = utils.preview(edges, verts, toShow, scn.resFloat/scn.ctmFloat, 1040 | polyLinesPoints, forcedEdges, gradedEdges, effective_lengths, obj.name, obj, disabled) 1041 | 1042 | cellstr = locale.format("%d", NoCells, grouping=True) 1043 | if not size: 1044 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(True,False,False)") 1045 | self.report({'INFO'}, "Preview mesh is empty. Too few vertices selected, or broken block structure!") 1046 | else: 1047 | self.report({'INFO'}, "Cells in mesh: " + cellstr) 1048 | 1049 | return{'FINISHED'} 1050 | 1051 | class OBJECT_OT_writeBMD(bpy.types.Operator): 1052 | '''Writes out a blockMeshDict file for the currently selected object''' 1053 | bl_idname = "write.bmdfile" 1054 | bl_label = "Write" 1055 | 1056 | if "bpy" in locals(): 1057 | import imp 1058 | if "utils" in locals(): 1059 | imp.reload(utils) 1060 | if "blender_utils" in locals(): 1061 | imp.reload(blender_utils) 1062 | 1063 | filepath = StringProperty( 1064 | name="File Path", 1065 | description="Filepath used for exporting the file", 1066 | maxlen=1024, 1067 | subtype='FILE_PATH', 1068 | default='/opt', 1069 | ) 1070 | check_existing = BoolProperty( 1071 | name="Check Existing", 1072 | description="Check and warn on overwriting existing files", 1073 | default=True, 1074 | options={'HIDDEN'}, 1075 | ) 1076 | 1077 | logging = BoolProperty( 1078 | name="Log", 1079 | description="Click to enable log files", 1080 | default=False 1081 | ) 1082 | 1083 | def invoke(self, context, event): 1084 | if context.scene.setEdges: 1085 | try: 1086 | bpy.data.objects[context.scene.geoobjName] 1087 | except: 1088 | self.report({'INFO'}, "Cannot find object for edges!") 1089 | return{'CANCELLED'} 1090 | try: 1091 | self.filepath = context.active_object['path'] 1092 | except: 1093 | self.filepath = 'blockMeshDict' 1094 | bpy.context.window_manager.fileselect_add(self) 1095 | return {'RUNNING_MODAL'} 1096 | 1097 | def execute(self, context): 1098 | import locale 1099 | from . import utils 1100 | import imp, math 1101 | imp.reload(utils) 1102 | from . import blender_utils 1103 | locale.setlocale(locale.LC_ALL, '') 1104 | 1105 | scn = context.scene 1106 | obj = context.active_object 1107 | patchnames = list() 1108 | patchtypes = list() 1109 | patchverts = list() 1110 | patches = list() 1111 | obj['path'] = self.filepath 1112 | 1113 | bpy.ops.object.mode_set(mode='OBJECT') # Refresh mesh object 1114 | bpy.ops.object.mode_set(mode='EDIT') 1115 | 1116 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(True,False,False)") 1117 | vertexNames = [] 1118 | disabled = [] 1119 | for group in obj.vertex_groups: 1120 | bpy.ops.object.mode_set(mode='EDIT') 1121 | bpy.ops.mesh.select_all(action='DESELECT') 1122 | if not group.name[0] == '_': 1123 | bpy.ops.object.vertex_group_set_active(group=group.name) 1124 | bpy.ops.object.vertex_group_select() 1125 | bpy.ops.object.mode_set(mode='OBJECT') 1126 | vlist = [] 1127 | for v in obj.data.vertices: 1128 | if v.select == True: 1129 | vlist += [v.index] 1130 | if vlist: 1131 | if group.name == 'disabled': 1132 | disabled = vlist 1133 | else: 1134 | vertexNames.append([group.name, vlist]) 1135 | 1136 | bpy.ops.object.mode_set(mode='EDIT') 1137 | for mid, m in enumerate(obj.data.materials): 1138 | bpy.ops.mesh.select_all(action='DESELECT') 1139 | obj.active_material_index = mid 1140 | bpy.ops.object.material_slot_select() 1141 | bpy.ops.object.mode_set(mode='OBJECT') 1142 | bpy.ops.object.mode_set(mode='EDIT') 1143 | faces = obj.data.polygons 1144 | for f in faces: 1145 | if f.select and f.material_index == mid: 1146 | if m.name in patchnames: 1147 | ind = patchnames.index(m.name) 1148 | patchverts[ind].append(list(f.vertices)) 1149 | else: 1150 | patchnames.append(m.name) 1151 | patchtypes.append(m['patchtype']) 1152 | patchverts.append([list(f.vertices)]) 1153 | 1154 | for ind,pt in enumerate(patchtypes): 1155 | patches.append([pt]) 1156 | patches[ind].append(patchnames[ind]) 1157 | patches[ind].append(patchverts[ind]) 1158 | 1159 | verts = list(blender_utils.vertices_from_mesh(obj)) 1160 | edges = list(blender_utils.edges_from_mesh(obj)) 1161 | 1162 | 1163 | forcedEdges = [] 1164 | for e in obj.data.edges: 1165 | if e.bevel_weight >= 0.001: 1166 | N = obj['bevelToResMap'][str(round(e.bevel_weight*100))] 1167 | forcedEdges.append([[e.vertices[0],e.vertices[1]], N]) 1168 | 1169 | gradedEdges = [] 1170 | for e in obj.data.edges: 1171 | if e.crease == 0: 1172 | grad = 1 1173 | else: 1174 | grad = obj['creaseToGradMap'][str(round(e.crease*100))] 1175 | gradedEdges.append([[e.vertices[0],e.vertices[1]], grad, e.use_seam]) 1176 | 1177 | bpy.ops.object.mode_set(mode='OBJECT') 1178 | obj.select = False 1179 | if scn.setEdges: 1180 | polyLines, polyLinesPoints, lengths = getPolyLines(verts, edges, obj) 1181 | else: 1182 | polyLines = '' 1183 | lengths = [[], []] 1184 | 1185 | dx0 = scn.resFloat 1186 | effective_lengths = [[], []] 1187 | for e in edges: 1188 | if e in lengths[0]: 1189 | ind = lengths[0].index(e) 1190 | length = lengths[1][ind] 1191 | elif [e[0],e[1]] in lengths[0]: 1192 | ind = lengths[0].index([e[0],e[1]]) 1193 | length = lengths[1][ind] 1194 | else: 1195 | length = (verts[e[0]] - verts[e[1]]).magnitude 1196 | 1197 | if length < 1e-6: 1198 | self.report({'INFO'}, "Zero length edge detected, check block structure!") 1199 | return{'FINISHED'} 1200 | 1201 | for ge in gradedEdges: 1202 | if ge[0] == e or ge[0] == [e[0],e[1]]: 1203 | grad = ge[1] 1204 | fine = ge[2] 1205 | break 1206 | if fine: #Fine cells should match the target resolution, this means fewer total cells 1207 | if grad > 1.00001: 1208 | if grad*dx0/length < 1: 1209 | length *= (math.log(grad)/(math.log(1-dx0/length)-math.log(1-grad*dx0/length)) +1) * dx0/length 1210 | elif grad < 0.99999: 1211 | if dx0/length < 1: 1212 | length /= (math.log(grad)/(math.log(1-dx0/length)-math.log(1-grad*dx0/length)) +1) * dx0/length 1213 | else: 1214 | pass 1215 | else: #Coarse cells should match the target resolution, this means more total cells 1216 | if grad > 1.00001: 1217 | if dx0/length < 1: 1218 | length *= (math.log(grad)/(math.log(1-dx0/(length*grad))-math.log(1-dx0/length)) +1) * dx0/length 1219 | elif grad < 0.99999: 1220 | if dx0/(length*grad) < 1: 1221 | length /= (math.log(grad)/(math.log(1-dx0/(length*grad))-math.log(1-dx0/length)) +1) * dx0/length 1222 | else: 1223 | pass 1224 | # Now we have a final effective edge length. Lets store! 1225 | effective_lengths[0].append([min(e[0],e[1]), max(e[0],e[1])]) # write out sorted 1226 | effective_lengths[1].append(length) 1227 | 1228 | obj.select = True 1229 | bpy.context.scene.objects.active = obj 1230 | 1231 | NoCells = utils.write(self.filepath, edges, verts, 1232 | scn.resFloat/scn.ctmFloat, scn.ctmFloat, 1233 | patches, polyLines, forcedEdges, gradedEdges, 1234 | effective_lengths, vertexNames, disabled, self.logging) 1235 | 1236 | cellstr = locale.format("%d", NoCells, grouping=True) 1237 | self.report({'INFO'}, "Cells in mesh: " + cellstr) 1238 | 1239 | return{'FINISHED'} 1240 | 1241 | 1242 | initProperties() 1243 | 1244 | def register(): 1245 | bpy.utils.register_module(__name__) 1246 | 1247 | def unregister(): 1248 | bpy.utils.unregister_module(__name__) 1249 | 1250 | -------------------------------------------------------------------------------- /blender_utils.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License 5 | # as published by the Free Software Foundation; either version 2 6 | # of the License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software Foundation, 15 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 | # 17 | # ##### END GPL LICENSE BLOCK ##### 18 | 19 | # 20 | 21 | import bpy 22 | 23 | 24 | 25 | def vertices_from_mesh(ob): 26 | ''' 27 | ''' 28 | 29 | # get the modifiers 30 | try: 31 | mesh = ob.to_mesh(bpy.context.scene, False, "PREVIEW") 32 | except RuntimeError: 33 | raise StopIteration 34 | 35 | matrix = ob.matrix_world.copy() 36 | 37 | for v in mesh.vertices: 38 | yield (matrix*v.co) 39 | 40 | bpy.data.meshes.remove(mesh) 41 | 42 | def edges_from_mesh(ob): 43 | ''' 44 | ''' 45 | 46 | # get the modifiers 47 | try: 48 | mesh = ob.to_mesh(bpy.context.scene, False, "PREVIEW") 49 | except RuntimeError: 50 | raise StopIteration 51 | 52 | for e in mesh.edges: 53 | yield list(e.vertices) 54 | 55 | bpy.data.meshes.remove(mesh) 56 | -------------------------------------------------------------------------------- /previewMesh.py: -------------------------------------------------------------------------------- 1 | # Code in this file is mainly taken from OpenFOAM source code ($FOAM_SRC/mesh/blockMesh) 2 | # For licence, see original files 3 | 4 | import mathutils 5 | 6 | class lineEdge: 7 | # Rip of OpenFOAM's lineEdge class 8 | def __init__(self, points): 9 | self.points_ = points 10 | 11 | def position(self, lambd): 12 | if (lambd < 0 or lambd > 1): 13 | print("error") 14 | return self.points_[0] + lambd * (self.points_[-1] - self.points_[0]) 15 | 16 | def length(self): 17 | return (points_[-1] - points_[0]).magnitude 18 | 19 | 20 | class polyLine: 21 | # Rip of OpenFOAM's polyLine class 22 | def __init__(self, start, end, points): 23 | 24 | self.points_ = points # list of mathtils.vectors 25 | self.param_ = [0.0]*len(self.points_) # list of doubles 26 | self.start_ = start 27 | self.end_ = end 28 | 29 | if (len(self.param_)): 30 | for i in range(len(self.param_)-1): #i=0 - 31 | self.param_[i+1] = self.param_[i] + (self.points_[i+1] - self.points_[i]).magnitude 32 | 33 | # normalize on the interval 0-1 34 | self.lineLength_ = self.param_[-1] 35 | self.param_[:] = [p/self.lineLength_ for p in self.param_] 36 | self.param_[-1] = 1.0 37 | else: 38 | self.lineLength_ = 0.0 39 | 40 | def compare(self, start, end): 41 | if (self.start_ == start and self.end_ == end): 42 | return 1; 43 | elif (self.start_ == end and self.end_ == start): 44 | return -1 45 | else: 46 | return 0 47 | 48 | def points(self): 49 | return self.points_ 50 | 51 | def nSegments(self): 52 | return len(self.points_)-1 53 | 54 | def localParameter(self, lambd): 55 | # check endpoints 56 | if (lambd < 1.e-10): 57 | return 0, 0 58 | elif (lambd > (1 - 1.e-10)): 59 | return 1, self.nSegments() 60 | 61 | # search table of cumulative distances to find which line-segment 62 | # we are on. Check the upper bound. 63 | 64 | segmentI = 1 65 | while (self.param_[segmentI] < lambd): 66 | segmentI += 1 67 | segmentI -= 1 # we want the corresponding lower bound 68 | 69 | # the local parameter [0-1] on this line segment 70 | lambd = ( 71 | ( lambd - self.param_[segmentI] ) 72 | / ( self.param_[segmentI+1] - self.param_[segmentI] )) 73 | 74 | return lambd, segmentI 75 | 76 | def position(self, mu): 77 | # check endpoints 78 | if (mu < 1.e-10): 79 | return self.points_[0] 80 | elif (mu > (1 - 1.e-10)): 81 | return self.points_[-1] 82 | 83 | lambd, segment = self.localParameter(mu) 84 | return self.position_segment(segment, lambd) 85 | 86 | def position_segment(self, segment, mu): 87 | # out-of-bounds 88 | if (segment < 0): 89 | return self.points_[0] 90 | elif (segment > self.nSegments()): 91 | return self.points_[-1] 92 | 93 | p0 = self.points()[segment] 94 | p1 = self.points()[segment+1] 95 | 96 | # special cases - no calculation needed 97 | if (mu <= 0.0): 98 | return p0 99 | elif (mu >= 1.0): 100 | return p1 101 | else: 102 | # linear interpolation 103 | return self.points_[segment] + mu * (p1 - p0) 104 | 105 | def length(self): 106 | return self.lineLength_ 107 | 108 | 109 | class lineDivide: 110 | # Rip of OpenFOAM's lineDivide class 111 | def __init__(self, cedge, ndiv, xratio): #cedge can be a polyLine or straightLine, xratio is from calcGexp 112 | self.points_ = [mathutils.Vector((0,0,0))]*(ndiv+1) #vectorlist 113 | self.divisions_ = [0.]*(ndiv+1) 114 | self.divisions_[ndiv] = 1.0; 115 | 116 | # calculate the spacing 117 | if (xratio == 1.0): 118 | for i in range(ndiv-1): #i = 1 ... ndiv-1 119 | self.divisions_[i+1] = float(i+1)/ndiv; 120 | else: 121 | for i in range(ndiv-1):# i = 1 ... ndiv-1 122 | self.divisions_[i+1] = (1.0 - pow(xratio, i+1))/(1.0 - pow(xratio, ndiv)); 123 | 124 | # calculate the points 125 | for i in range(ndiv+1): 126 | self.points_[i] = cedge.position(self.divisions_[i]); 127 | 128 | def points(self): 129 | return self.points_; 130 | 131 | def lambdaDivisions(self): 132 | return self.divisions_; 133 | 134 | 135 | def buildPreviewMesh(corners, vertices_coord, ni, nj, nk, polyLinesPoints, gradList, vertexNo, preview_verts, preview_edges, preview_faces): 136 | import bpy 137 | def vtxLabel(i, j, k, ni, nj, nk): # surface mesh... looks messy 138 | def f(ind, maxind): 139 | return 0 if (ind < maxind) else 1 140 | if k == 0: 141 | return i + j * (ni + 1) 142 | if k>0 and k0 and j1) else 0.0 155 | 156 | offset = vertexNo 157 | 158 | p000 = vertices_coord[corners[0]] 159 | p100 = vertices_coord[corners[1]] 160 | p110 = vertices_coord[corners[2]] 161 | p010 = vertices_coord[corners[3]] 162 | 163 | p001 = vertices_coord[corners[4]] 164 | p101 = vertices_coord[corners[5]] 165 | p111 = vertices_coord[corners[6]] 166 | p011 = vertices_coord[corners[7]] 167 | 168 | p = [[]*3]*12 # init 169 | w = [[1]*3]*12 # init 170 | 171 | edgeOrder = [[0,1], [3,2], [7,6], [4,5], [0,3], [1,2], [5,6], [4,7], [0,4], [1,5], [2,6], [3,7] ] 172 | n = [ni]*4 + [nj]*4 + [nk]*4 173 | for plp in polyLinesPoints: 174 | for edgeID in range(12): 175 | c0= corners[edgeOrder[edgeID][0]] 176 | c1 = corners[edgeOrder[edgeID][1]] 177 | if c0 in plp and c1 in plp: 178 | cedge = polyLine(plp[0], plp[1], plp[2]) 179 | cmp = cedge.compare(c0, c1) 180 | if (cmp > 0): 181 | lineDivObj = lineDivide(cedge, n[edgeID], calcGexp(gradList[edgeID], n[edgeID])) 182 | p[edgeID] = lineDivObj.points() 183 | w[edgeID] = lineDivObj.lambdaDivisions() 184 | else: 185 | lineDivObj = lineDivide(cedge, n[edgeID], 1.0/(calcGexp(gradList[edgeID], n[edgeID])+1e-10) ) 186 | p[edgeID] = lineDivObj.points() 187 | w[edgeID] = lineDivObj.lambdaDivisions() 188 | p[edgeID].reverse() 189 | w[edgeID].reverse() 190 | for i in range(len(w[edgeID])): 191 | w[edgeID][i] = 1.0 - w[edgeID][i] 192 | 193 | for edgeID in range(12): 194 | if not p[edgeID]: # was not set above == is a straight line 195 | p0 = vertices_coord[corners[edgeOrder[edgeID][0]]] 196 | p1 = vertices_coord[corners[edgeOrder[edgeID][1]]] 197 | lineObj = lineEdge([p0,p1]) 198 | n[edgeID] 199 | lineDivObj = lineDivide(lineObj, n[edgeID], calcGexp(gradList[edgeID], n[edgeID])) 200 | p[edgeID] = lineDivObj.points() 201 | w[edgeID] = lineDivObj.lambdaDivisions() 202 | 203 | preview_verts += [0 for i in range(( (ni+1) * (nj+1) ) * 2 + (ni*2+nj*2) * (nk-1))] 204 | 205 | # generate vertices and edges 206 | # 207 | for k in range(nk+1): 208 | for j in range(nj+1): 209 | for i in range(ni+1): 210 | if k==0 or k==nk or j==0 or j==nj or i==0 or i==ni: 211 | vertexNo = vtxLabel(i, j, k, ni, nj, nk) + offset 212 | 213 | # points on edges 214 | edgex1 = p000 + (p100 - p000)*w[0][i] # 0 1 215 | edgex2 = p010 + (p110 - p010)*w[1][i] # 3 2 216 | edgex3 = p011 + (p111 - p011)*w[2][i] # 7 6 217 | edgex4 = p001 + (p101 - p001)*w[3][i] # 4 5 218 | 219 | edgey1 = p000 + (p010 - p000)*w[4][j] # 0 3 220 | edgey2 = p100 + (p110 - p100)*w[5][j] # 1 2 221 | edgey3 = p101 + (p111 - p101)*w[6][j] # 5 6 222 | edgey4 = p001 + (p011 - p001)*w[7][j] # 4 7 223 | 224 | edgez1 = p000 + (p001 - p000)*w[8][k] # 0 4 225 | edgez2 = p100 + (p101 - p100)*w[9][k] # 1 5 226 | edgez3 = p110 + (p111 - p110)*w[10][k]# 2 6 227 | edgez4 = p010 + (p011 - p010)*w[11][k]# 3 7 228 | 229 | 230 | # calculate the importance factors for all edges 231 | 232 | # x-direction 233 | impx1 = ( 234 | (1.0 - w[0][i])*(1.0 - w[4][j])*(1.0 - w[8][k]) 235 | + w[0][i]*(1.0 - w[5][j])*(1.0 - w[9][k]) 236 | ) 237 | 238 | impx2 = ( 239 | (1.0 - w[1][i])*w[4][j]*(1.0 - w[11][k]) 240 | + w[1][i]*w[5][j]*(1.0 - w[10][k]) 241 | ) 242 | 243 | impx3 = ( 244 | (1.0 - w[2][i])*w[7][j]*w[11][k] 245 | + w[2][i]*w[6][j]*w[10][k] 246 | ) 247 | 248 | impx4 = ( 249 | (1.0 - w[3][i])*(1.0 - w[7][j])*w[8][k] 250 | + w[3][i]*(1.0 - w[6][j])*w[9][k] 251 | ) 252 | 253 | magImpx = impx1 + impx2 + impx3 + impx4 254 | impx1 /= magImpx 255 | impx2 /= magImpx 256 | impx3 /= magImpx 257 | impx4 /= magImpx 258 | 259 | 260 | # y-direction 261 | impy1 = ( 262 | (1.0 - w[4][j])*(1.0 - w[0][i])*(1.0 - w[8][k]) 263 | + w[4][j]*(1.0 - w[1][i])*(1.0 - w[11][k]) 264 | ) 265 | 266 | impy2 = ( 267 | (1.0 - w[5][j])*w[0][i]*(1.0 - w[9][k]) 268 | + w[5][j]*w[1][i]*(1.0 - w[10][k]) 269 | ) 270 | 271 | impy3 = ( 272 | (1.0 - w[6][j])*w[3][i]*w[9][k] 273 | + w[6][j]*w[2][i]*w[10][k] 274 | ) 275 | 276 | impy4 = ( 277 | (1.0 - w[7][j])*(1.0 - w[3][i])*w[8][k] 278 | + w[7][j]*(1.0 - w[2][i])*w[11][k] 279 | ) 280 | 281 | magImpy = impy1 + impy2 + impy3 + impy4 282 | impy1 /= magImpy 283 | impy2 /= magImpy 284 | impy3 /= magImpy 285 | impy4 /= magImpy 286 | 287 | 288 | # z-direction 289 | impz1 = ( 290 | (1.0 - w[8][k])*(1.0 - w[0][i])*(1.0 - w[4][j]) 291 | + w[8][k]*(1.0 - w[3][i])*(1.0 - w[7][j]) 292 | ) 293 | 294 | impz2 = ( 295 | (1.0 - w[9][k])*w[0][i]*(1.0 - w[5][j]) 296 | + w[9][k]*w[3][i]*(1.0 - w[6][j]) 297 | ) 298 | 299 | impz3 = ( 300 | (1.0 - w[10][k])*w[1][i]*w[5][j] 301 | + w[10][k]*w[2][i]*w[6][j] 302 | ) 303 | 304 | impz4 = ( 305 | (1.0 - w[11][k])*(1.0 - w[1][i])*w[4][j] 306 | + w[11][k]*(1.0 - w[2][i])*w[7][j] 307 | ) 308 | 309 | magImpz = impz1 + impz2 + impz3 + impz4 310 | impz1 /= magImpz 311 | impz2 /= magImpz 312 | impz3 /= magImpz 313 | impz4 /= magImpz 314 | 315 | 316 | # calculate the correction vectors 317 | corx1 = impx1*(p[0][i] - edgex1) 318 | corx2 = impx2*(p[1][i] - edgex2) 319 | corx3 = impx3*(p[2][i] - edgex3) 320 | corx4 = impx4*(p[3][i] - edgex4) 321 | 322 | cory1 = impy1*(p[4][j] - edgey1) 323 | cory2 = impy2*(p[5][j] - edgey2) 324 | cory3 = impy3*(p[6][j] - edgey3) 325 | cory4 = impy4*(p[7][j] - edgey4) 326 | 327 | corz1 = impz1*(p[8][k] - edgez1) 328 | corz2 = impz2*(p[9][k] - edgez2) 329 | corz3 = impz3*(p[10][k] - edgez3) 330 | corz4 = impz4*(p[11][k] - edgez4) 331 | 332 | 333 | # multiply by the importance factor 334 | 335 | # x-direction 336 | edgex1 *= impx1 337 | edgex2 *= impx2 338 | edgex3 *= impx3 339 | edgex4 *= impx4 340 | 341 | # y-direction 342 | edgey1 *= impy1 343 | edgey2 *= impy2 344 | edgey3 *= impy3 345 | edgey4 *= impy4 346 | 347 | # z-direction 348 | edgez1 *= impz1 349 | edgez2 *= impz2 350 | edgez3 *= impz3 351 | edgez4 *= impz4 352 | 353 | 354 | # add the contributions 355 | preview_verts[vertexNo] = ( 356 | edgex1 + edgex2 + edgex3 + edgex4 357 | + edgey1 + edgey2 + edgey3 + edgey4 358 | + edgez1 + edgez2 + edgez3 + edgez4 359 | ) / 3.0 360 | 361 | preview_verts[vertexNo] += ( 362 | corx1 + corx2 + corx3 + corx4 363 | + cory1 + cory2 + cory3 + cory4 364 | + corz1 + corz2 + corz3 + corz4 365 | ) 366 | if (k == 0 or k == nk) and i>0 and j>0: #interior 367 | # preview_edges.append([vertexNo, vtxLabel(i-1, j, k, ni, nj, nk) + offset]) 368 | # preview_edges.append([vertexNo, vtxLabel(i, j-1, k, ni, nj, nk) + offset]) 369 | preview_faces.append([vtxLabel(i-1, j-1, k, ni, nj, nk) + offset, vtxLabel(i-1, j, k, ni, nj, nk) + offset, vtxLabel(i, j, k, ni, nj, nk) + offset, vtxLabel(i, j-1, k, ni, nj, nk) + offset]) 370 | # elif (k == 0 or k == nk) and (i == 0 or i == ni ) and j>0: 371 | # preview_edges.append([vertexNo, vtxLabel(i, j-1, k, ni, nj, nk) + offset]) 372 | # elif (k == 0 or k == nk) and (j == 0 or j == nj) and i>0: 373 | # preview_edges.append([vertexNo, vtxLabel(i-1, j, k, ni, nj, nk) + offset]) 374 | if k>0 and (i == 0 or i == ni): 375 | # preview_edges.append([vertexNo, vtxLabel(i, j, k-1, ni, nj, nk) + offset]) 376 | if j>0: 377 | # preview_edges.append([vertexNo, vtxLabel(i, j-1, k, ni, nj, nk) + offset]) 378 | preview_faces.append([vtxLabel(i, j-1, k-1, ni, nj, nk) + offset, vtxLabel(i, j-1, k, ni, nj, nk) + offset, vtxLabel(i, j, k, ni, nj, nk) + offset, vtxLabel(i, j, k-1, ni, nj, nk) + offset]) 379 | if k>0 and (j == 0 or j == nj): 380 | # preview_edges.append([vertexNo, vtxLabel(i, j, k-1, ni, nj, nk) + offset]) 381 | if i>0: 382 | # preview_edges.append([vertexNo, vtxLabel(i-1, j, k, ni, nj, nk) + offset]) 383 | preview_faces.append([vtxLabel(i-1, j, k-1, ni, nj, nk) + offset, vtxLabel(i-1, j, k, ni, nj, nk) + offset, vtxLabel(i, j, k, ni, nj, nk) + offset, vtxLabel(i, j, k-1, ni, nj, nk) + offset]) 384 | 385 | 386 | 387 | return ( (ni+1) * (nj+1) ) * 2 + (ni*2+nj*2) * (nk-1) + offset, preview_verts, preview_edges, preview_faces 388 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License 5 | # as published by the Free Software Foundation; either version 2 6 | # of the License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software Foundation, 15 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 | # 17 | # ##### END GPL LICENSE BLOCK ##### 18 | 19 | ''' 20 | ''' 21 | 22 | import mathutils 23 | import itertools 24 | 25 | def write(filename, edges, vertices_coord, mean_res, convertToMeters, patchnames, polyLines, forcedEdges, gradEdges, lengths, vertexNames, disabled, logging): 26 | 27 | if logging: 28 | logFileName = filename.replace('blockMeshDict','log.swiftblock') 29 | debugFileName = filename.replace('blockMeshDict','facesFound.obj') 30 | else: 31 | logFileName = '' 32 | debugFileName = '' 33 | 34 | patchfaces = [] 35 | for pn in patchnames: 36 | for vl in pn[2]: 37 | patchfaces.append(vl) 38 | 39 | # Get the blockstructure, which edges that have the same #of cells, some info on face, and edges-in-use 40 | logFile, block_print_out, dependent_edges, face_info, all_edges, faces_as_list_of_nodes = blockFinder(edges, vertices_coord, logFileName, debugFileName, disabled) 41 | 42 | # Set edges resolution 43 | edgeRes = setEdgeRes(vertices_coord, dependent_edges, forcedEdges, mean_res, lengths) 44 | 45 | # offendingBlocks = [] 46 | 47 | bmFile = open(filename,'w') 48 | bmFile.write(foamHeader()) 49 | bmFile.write("\nconvertToMeters " + str(convertToMeters) + ";\n\nvertices\n(\n") 50 | 51 | for v in vertices_coord: 52 | bmFile.write(' ({} {} {})\n'.format(*v)) 53 | bmFile.write(");\nblocks\n(\n") 54 | 55 | NoCells = 0 56 | for bid, vl in enumerate(block_print_out): 57 | blockName = '' 58 | for name in reversed(vertexNames): 59 | if all( v in name[1] for v in vl ): 60 | blockName = name[0] 61 | for es, edgeSet in enumerate(dependent_edges): 62 | if edge(vl[0],vl[1]) in edgeSet: 63 | ires = edgeRes[es] 64 | if edge(vl[1],vl[2]) in edgeSet: 65 | jres = edgeRes[es] 66 | if edge(vl[0],vl[4]) in edgeSet: 67 | kres = edgeRes[es] 68 | NoCells += ires*jres*kres 69 | gradStr = getGrading(vl, gradEdges, []) 70 | bmFile.write(' hex ({} {} {} {} {} {} {} {}) '.format(*vl) \ 71 | + blockName + ' ({} {} {}) '.format(ires,jres,kres)\ 72 | + gradStr + '\n' ) 73 | 74 | # for f in face_info: 75 | # for bid in offendingBlocks: 76 | # if bid in face_info[f]['pos']: 77 | # face_info[f]['pos'].pop(face_info[f]['pos'].index(bid)) 78 | # if bid in face_info[f]['neg']: 79 | # face_info[f]['neg'].pop(face_info[f]['neg'].index(bid)) 80 | 81 | bmFile.write(');\n\npatches\n(\n') 82 | 83 | for pn in patchnames: 84 | if not len(pn[2]) == 0: 85 | bmFile.write(' {} {}\n (\n'.format(pn[0],pn[1])) 86 | for pl in pn[2]: 87 | fid, tmp = findFace(faces_as_list_of_nodes, pl) 88 | if fid >= 0: 89 | if (len(face_info[fid]['neg']) + len(face_info[fid]['pos'])) == 1: #avoid printing internal faces and patches in non-identified blocks as patch 90 | bmFile.write(' ({} {} {} {})\n'.format(*pl)) 91 | bmFile.write(' )\n') 92 | 93 | bmFile.write(');\n\nedges\n(\n') 94 | 95 | for pl in polyLines: 96 | bmFile.write(pl) 97 | 98 | bmFile.write(foamFileEnd()) 99 | bmFile.close() 100 | return NoCells 101 | 102 | 103 | def preview(edges, vertices_coord, toShow, mean_res, polyLinesPoints, forcedEdges, gradEdges, lengths, objname, obj, disabled): 104 | from . import previewMesh 105 | import imp, bpy 106 | imp.reload(previewMesh) 107 | 108 | # Get the blockstructure, which edges that have the same #of cells, some info on face, and edges-in-use 109 | logFile, block_print_out, dependent_edges, face_info, all_edges, faces_as_list_of_nodes = blockFinder(edges, vertices_coord, '','', disabled) 110 | 111 | # Set edges resolution 112 | edgeRes = setEdgeRes(vertices_coord, dependent_edges, forcedEdges, mean_res, lengths) 113 | 114 | preview_verts = [] 115 | preview_edges = [] 116 | preview_faces = [] 117 | NoPreviewVertex = 0 118 | NoCells = 0 119 | for bid, vl in enumerate(block_print_out): 120 | for es, edgeSet in enumerate(dependent_edges): 121 | if edge(vl[0],vl[1]) in edgeSet: 122 | ires = edgeRes[es] 123 | if edge(vl[1],vl[2]) in edgeSet: 124 | jres = edgeRes[es] 125 | if edge(vl[0],vl[4]) in edgeSet: 126 | kres = edgeRes[es] 127 | NoCells += ires*jres*kres 128 | gradList = [] 129 | gradStr = getGrading(vl, gradEdges, gradList) 130 | showThisBlock = 1 131 | for v in vl: 132 | showThisBlock *= toShow[v] 133 | if showThisBlock: 134 | NoPreviewVertex, preview_verts, preview_edges, preview_faces = previewMesh.buildPreviewMesh( 135 | vl, vertices_coord, ires,jres,kres, 136 | polyLinesPoints, gradList, NoPreviewVertex, 137 | preview_verts, preview_edges, preview_faces) 138 | 139 | mesh_data = bpy.data.meshes.new("previewmesh") 140 | mesh_data.from_pydata(preview_verts, [], preview_faces) 141 | mesh_data.update() 142 | preview_obj = bpy.data.objects.new('PreviewMesh', mesh_data) 143 | bpy.context.scene.objects.link(preview_obj) 144 | preview_obj.select = True 145 | bpy.context.scene.objects.active = bpy.data.objects['PreviewMesh'] 146 | preview_obj['swiftBlockObj'] = objname 147 | bpy.context.scene.objects.active = preview_obj 148 | bpy.ops.object.mode_set(mode='EDIT') 149 | bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=True) 150 | bpy.ops.object.mode_set(mode='OBJECT') 151 | bpy.ops.object.mode_set(mode='EDIT') 152 | bpy.ops.mesh.select_all(action='DESELECT') 153 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(False,True,False)") 154 | obj.hide = True 155 | 156 | return len(preview_obj.data.vertices), NoCells 157 | 158 | def repairFaces(edges, vertices_coord, disabled, obj, removeInternal, createBoundary): 159 | import bpy 160 | 161 | # Get the blockstructure, which edges that have the same #of cells, some info on face, and edges-in-use 162 | logFile, block_print_out, dependent_edges, face_info, all_edges, faces_as_list_of_nodes = blockFinder(edges, vertices_coord, '','', disabled) 163 | 164 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(False,False,True)") 165 | 166 | nRemoved = 0 167 | if removeInternal: 168 | bpy.ops.object.mode_set(mode='EDIT') 169 | bpy.ops.mesh.select_all(action='DESELECT') 170 | bpy.ops.object.mode_set(mode='OBJECT') 171 | for f in obj.data.polygons: 172 | fid, tmp = findFace(faces_as_list_of_nodes, f.vertices) 173 | if fid >= 0: # face was found in list 174 | if (len(face_info[fid]['neg']) + len(face_info[fid]['pos'])) > 1: #this is an internal face 175 | f.select = True 176 | nRemoved += 1 177 | bpy.ops.object.mode_set(mode='EDIT') 178 | bpy.ops.mesh.delete(type='ONLY_FACE') 179 | bpy.ops.object.mode_set(mode='OBJECT') 180 | bpy.ops.object.mode_set(mode='EDIT') 181 | bpy.ops.mesh.select_all(action='DESELECT') 182 | bpy.ops.object.mode_set(mode='OBJECT') 183 | 184 | nCreated = 0 185 | if createBoundary: 186 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(True,False,False)") 187 | presentFaces = [] 188 | 189 | for f in obj.data.polygons: 190 | presentFaces.append(list(f.vertices)) 191 | 192 | for faceid, f in enumerate(faces_as_list_of_nodes): 193 | if (len(face_info[faceid]['neg']) + len(face_info[faceid]['pos'])) == 1: #this is a boundary face 194 | fid, tmp = findFace(presentFaces, f) 195 | if fid < 0: # this boundary face does not exist as a blender polygon. lets create one! 196 | bpy.ops.object.mode_set(mode='EDIT') 197 | bpy.ops.mesh.select_all(action='DESELECT') 198 | bpy.ops.object.mode_set(mode='OBJECT') 199 | for v in f: 200 | obj.data.vertices[v].select = True 201 | bpy.ops.object.mode_set(mode='EDIT') 202 | bpy.ops.mesh.edge_face_add() 203 | bpy.ops.mesh.tris_convert_to_quads(limit=3.14159, uvs=False, vcols=False, sharp=False, materials=False) 204 | nCreated += 1 205 | 206 | bpy.ops.object.mode_set(mode='EDIT') 207 | bpy.ops.mesh.select_all(action='DESELECT') 208 | return nRemoved, nCreated 209 | 210 | # ------------------------------------------------------------------------------- # 211 | 212 | 213 | def removedup(seq): 214 | checked = [] 215 | for e in seq: 216 | if e not in checked: 217 | checked.append(e) 218 | return checked 219 | 220 | def edge(e0, e1): 221 | return [min(e0,e1), max(e0,e1)] 222 | 223 | def couple_edges(dependent_edges): 224 | for es0, edgeSet0 in enumerate(dependent_edges): 225 | for edge in edgeSet0: 226 | for es1, edgeSet1 in enumerate(dependent_edges): 227 | if edge in edgeSet1 and es0 != es1: 228 | for e in edgeSet0: 229 | edgeSet1.append(e) 230 | dependent_edges.pop(es0) 231 | return True 232 | return False 233 | 234 | def setEdgeRes(vertices_coord, dependent_edges, forcedEdges, mean_res, lengths): 235 | edgeRes = [] 236 | for es in dependent_edges: 237 | edgeLength = 0. 238 | for e in es: 239 | if edge(e[0],e[1]) in lengths[0]: 240 | ind = lengths[0].index(edge(e[0],e[1])) 241 | edgeLength += lengths[1][ind] 242 | else: 243 | print("Cannot find edge in lengths!") 244 | edgeRes.append(max(1,round(edgeLength / (mean_res * len(es))))) 245 | for fed in forcedEdges: 246 | v0, v1 = fed[0][0],fed[0][1] 247 | if edge(v0,v1) in es: 248 | edgeRes[-1] = fed[1] 249 | return edgeRes 250 | 251 | def getGrading(vl, gradEdges, listOfGrading): 252 | edges = [] 253 | grading = [] 254 | gradList = [] 255 | gradStr = "edgeGrading (" 256 | for e in gradEdges: # split up in two lists 257 | edges.append(e[0]) 258 | grading.append(e[1]) 259 | 260 | def lookupGrad(v0,v1,edges,grading): 261 | if [v0,v1] in edges: 262 | return grading[edges.index([v0,v1])] 263 | else: 264 | grad = grading[edges.index([v1,v0])] 265 | if grad < 0: 266 | return grad # double grading feature 267 | else: 268 | return 1.0/grad 269 | 270 | edgeOrder = [[0,1], [3,2], [7,6], [4,5], [0,3], [1,2], [5,6], [4,7], [0,4], [1,5], [2,6], [3,7]] 271 | for e in edgeOrder: 272 | v0 = vl[e[0]] 273 | v1 = vl[e[1]] 274 | gradList.append(lookupGrad(v0,v1, edges,grading)) 275 | gradStr += " " + str(lookupGrad(v0,v1, edges,grading)) 276 | listOfGrading += [lookupGrad(v0,v1, edges,grading)] 277 | return gradStr + ")" 278 | 279 | 280 | def findFace(faces, vl): 281 | for fid, f in enumerate(faces): 282 | if vl[0] in f and vl[1] in f and vl[2] in f and vl[3] in f: 283 | return fid, f 284 | return -1, [] 285 | 286 | 287 | class cycleFinder: 288 | # Credit: Adam Gaither. An Efficient Block Detection Algorithm For Structured Grid Generation 289 | # 290 | def __init__(self, edges, verts): 291 | self.edges = edges 292 | self.verticesId = verts 293 | self.edgeVisited = [False for i in range(len(edges))] 294 | self.currentCycle = [] 295 | self.currentCycleEdges = [] 296 | self.faces = [] 297 | self.facesEdges = [] 298 | self.facesId = [] 299 | self.no_edges = 0 300 | self.v_in_edge = [[] for i in range(len(verts))] 301 | for v in verts: 302 | append = self.v_in_edge[v].append 303 | for eid, e in enumerate(self.edges): 304 | if v in e: 305 | append(eid) 306 | 307 | def buildAllFourEdgeFaces(self): 308 | for v in self.verticesId: 309 | self.currentCycle = [] 310 | self.currentCycleEdges = [] 311 | self.currentCycle.append(v) 312 | self.buildFourEdgeFaces(v) 313 | self.VisitAllEdgeAdjacent(v) 314 | return self.faces,self.facesEdges 315 | 316 | def buildFourEdgeFaces(self, v): 317 | for eid in self.v_in_edge[v]: 318 | if not self.edgeVisited[eid]: 319 | e = self.edges[eid] 320 | self.no_edges += 1 321 | self.edgeVisited[eid] = True 322 | opposite_v = e[0] 323 | if opposite_v == v: # seems the other vertex is in e[1]! 324 | opposite_v = e[1] 325 | self.currentCycle.append(opposite_v) 326 | self.currentCycleEdges.append(eid) 327 | if self.currentCycle[0] == self.currentCycle[-1]: # First equals last -> we have a face 328 | if self.uniqueCycle(): 329 | self.faces.append(self.currentCycle[0:-1]) 330 | self.facesEdges.append(self.currentCycleEdges[:]) 331 | self.facesId.append(self.currentCycle[0:-1]) 332 | self.facesId[-1].sort() 333 | else: 334 | if self.no_edges < 4: 335 | self.buildFourEdgeFaces(opposite_v) 336 | self.no_edges -= 1 337 | self.currentCycle.pop() 338 | self.currentCycleEdges.pop() 339 | self.edgeVisited[eid] = False 340 | 341 | def uniqueCycle(self): 342 | cF = self.currentCycle[0:-1] 343 | cF.sort() 344 | return not cF in self.facesId 345 | 346 | def VisitAllEdgeAdjacent(self, v): 347 | for eid, e in enumerate(self.edges): 348 | if v in e: 349 | self.edgeVisited[eid] 350 | 351 | 352 | def blockFinder(edges, vertices_coord, logFileName='', debugFileName='', disabled = []): 353 | if len(logFileName) > 0: 354 | logFile = open(logFileName,'w') 355 | else: 356 | logFile = '' 357 | 358 | # Use the cycle finder class to find all edges forming quad faces 359 | cycFindFaces = cycleFinder(edges,range(len(vertices_coord))) 360 | faces_as_list_of_vertices = [] 361 | faces_as_list_of_nodes = [] 362 | faces_as_list_of_edges = [] 363 | tmp_v,tmp_e = cycFindFaces.buildAllFourEdgeFaces() 364 | 365 | for ii, i in enumerate(tmp_v): # get rid of possible triangles 366 | if len(i) == 4: 367 | faces_as_list_of_vertices.append([vertices_coord[i[0]], vertices_coord[i[1]], vertices_coord[i[2]], vertices_coord[i[3]]]) 368 | faces_as_list_of_nodes.append(i) 369 | faces_as_list_of_edges.append(tmp_e[ii]) 370 | 371 | # Create a wavefront obj file showing all the faces just found 372 | if len(debugFileName) > 0: 373 | debugFile = open(debugFileName,'w') 374 | for v in vertices_coord: 375 | debugFile.write('v {} {} {}\n'.format(*v)) 376 | for f in faces_as_list_of_nodes: 377 | debugFile.write('f ') 378 | for n in f: 379 | debugFile.write('{} '.format(n+1)) 380 | debugFile.write('\n') 381 | debugFile.close() 382 | 383 | # Store some info for the faces in a dict 384 | face_info = {} 385 | for fid, f in enumerate(faces_as_list_of_vertices): 386 | normal = mathutils.geometry.normal(f[0],f[1],f[2],f[3]) 387 | facecentre = mathutils.Vector((0,0,0)) 388 | for v in f: 389 | facecentre += 0.25*v 390 | face_info[fid] = {} 391 | face_info[fid]['normal'] = normal 392 | face_info[fid]['pos'] = [] 393 | face_info[fid]['neg'] = [] 394 | face_info[fid]['centre'] = facecentre 395 | 396 | connections_between_faces = [] 397 | 398 | # Find connections between faces, i.e. they share one edge 399 | for fid1, f1 in enumerate(faces_as_list_of_edges): 400 | for e in f1: 401 | for fid2, f2 in enumerate(faces_as_list_of_edges): 402 | if e in f2 and not fid1 == fid2: 403 | if not [min(fid1,fid2),max(fid1,fid2)] in connections_between_faces: 404 | connections_between_faces.append([min(fid1,fid2),max(fid1,fid2)]) 405 | 406 | # Use these connections to find cycles of connected faces; called faceLoops 407 | cycFindFaceLoops = cycleFinder(connections_between_faces,range(len(faces_as_list_of_vertices))) 408 | faceLoops_as_list_of_faces, faceLoops_as_list_of_connections = cycFindFaceLoops.buildAllFourEdgeFaces() 409 | 410 | # Dig out block structures from these face loops 411 | block_as_faceLoop = [] 412 | for qf in faceLoops_as_list_of_faces: 413 | qf_is_a_block = True 414 | for n in faces_as_list_of_nodes[qf[0]]: 415 | if n in faces_as_list_of_nodes[qf[2]]: #if any of the vertices in face 0 is in face 2, this is not a block 416 | qf_is_a_block = False 417 | if qf_is_a_block: 418 | block_as_faceLoop.append(qf) 419 | 420 | # Get rid of block dublets - there are plenty 421 | faceLoops_nodes = [[] for i in range(len(block_as_faceLoop))] 422 | for qfid, qf in enumerate(block_as_faceLoop): 423 | for f in qf: 424 | for n in faces_as_list_of_nodes[f]: 425 | if not n in faceLoops_nodes[qfid]: 426 | faceLoops_nodes[qfid].append(n) 427 | for qf in faceLoops_nodes: 428 | qf.sort() 429 | 430 | tmp = [] 431 | potentialBlocks = [] # Each block is identified several times. Condense and put in potentialBlocks (list of vertices index) 432 | for qfid, qf in enumerate(faceLoops_nodes): 433 | if not qf in tmp: 434 | tmp.append(qf) 435 | if len(qf) == 8: 436 | potentialBlocks.append(block_as_faceLoop[qfid]) 437 | 438 | offences = [] 439 | block_centres = [] 440 | formalBlocks = [] 441 | dependent_edges = [] 442 | all_edges = [] 443 | if len(logFileName) > 0: 444 | logFile.write('number of potential blocks identified = ' + str(len(potentialBlocks)) + '\n') 445 | 446 | for b in potentialBlocks: 447 | is_a_real_block = True # more sanity checks soon... 448 | block = [] 449 | for n in faces_as_list_of_nodes[b[0]]: 450 | block.append(n) 451 | for n in faces_as_list_of_nodes[b[2]]: 452 | block.append(n) 453 | q2start = None 454 | for e in edges: # Locate the vertex just above block[0]. Store as q2start 455 | if block[0] == e[0]: 456 | if e[1] in block[4:8]: 457 | q2start = block.index(e[1]) 458 | if block[0] == e[1]: 459 | if e[0] in block[4:8]: 460 | q2start = block.index(e[0]) 461 | if q2start == None: # if not found above - this is not a complete block. 462 | q1nodes = block[0:4] 463 | q2nodes = block[4:-1] 464 | if len(logFileName) > 0: 465 | logFile.write('one block found was incomplete! ' + str(q1nodes) + str(q2nodes) + '\n') 466 | continue 467 | q2start = 0 #just set it to something. block wont be printed anyway 468 | quad1 = block[0:4] 469 | quad2 = [] 470 | for i in range(4): 471 | quad2.append(block[(i + q2start) % 4 + 4]) 472 | q1verts = [vertices_coord[quad1[0]],vertices_coord[quad1[1]],vertices_coord[quad1[2]],vertices_coord[quad1[3]]] 473 | q2verts = [vertices_coord[quad2[0]],vertices_coord[quad2[1]],vertices_coord[quad2[2]],vertices_coord[quad2[3]]] 474 | 475 | blockcentre = mathutils.Vector((0,0,0)) 476 | for n in block: 477 | blockcentre += 0.125*vertices_coord[n] 478 | q1fid, tmp = findFace(faces_as_list_of_nodes, quad1) 479 | q2fid, tmp = findFace(faces_as_list_of_nodes, quad2) 480 | 481 | normal1 = mathutils.geometry.normal(*q1verts) 482 | normal2 = mathutils.geometry.normal(*q2verts) 483 | 484 | facecentre1 = face_info[q1fid]['centre'] 485 | facecentre2 = face_info[q2fid]['centre'] 486 | direction1 = blockcentre-facecentre1 487 | direction2 = blockcentre-facecentre2 488 | 489 | v04 = q2verts[0] - q1verts[0] 490 | scalarProd1 = direction1.dot(normal1) 491 | scalarProd2 = direction2.dot(normal2) 492 | scalarProd3 = normal1.dot(v04) 493 | 494 | if scalarProd1*scalarProd2 > 0.: # make quad1 and quad2 rotate in the same direction 495 | quad2 = [quad2[0], quad2[-1], quad2[-2], quad2[-3]] 496 | normal2 *= -1.0 497 | 498 | if scalarProd3 < 0.: # Maintain righthanded system in each block 499 | tmp = list(quad2) 500 | quad2 = list(quad1) 501 | quad1 = tmp 502 | 503 | for nid,n in enumerate(quad1): #check that all edges are present 504 | if not (([n,quad2[nid]] in edges) or ([quad2[nid],n] in edges)): 505 | if len(logFileName) > 0: 506 | logFile.write('one block did not have all edges! ' + str(quad1) + str(quad2) + '\n') 507 | is_a_real_block = False 508 | break 509 | if not is_a_real_block: 510 | continue 511 | # more sanity... 512 | scale = v04.magnitude * normal1.magnitude 513 | if (abs(scalarProd3/scale) < 0.01): # abs(sin(alpha)) < 0.01, where alpha is angle for normal1 and v04 514 | if len(logFileName) > 0: 515 | logFile.write('flat block ruled out!' + str(quad1) + str(quad2) + '\n') 516 | continue 517 | 518 | if is_a_real_block: # this write-out only works if blenders own vertex numbering starts at zero!! seems to work... 519 | offences.append(0) 520 | block_centres.append(blockcentre) 521 | 522 | vl = quad1 + quad2 523 | formalBlocks.append(vl) # list of verts defining the block in correct order 524 | 525 | # formalBlocks are blocks that hava formal block structure and are not flat. Still in an O-mesh there are more formal 526 | # blocks present than what we want. More filtering... 527 | 528 | for bid, vl in enumerate(formalBlocks): 529 | fs = [] 530 | fs.append(vl[0:4]) 531 | fs.append(vl[4:8]) 532 | fs.append([vl[0], vl[1], vl[5], vl[4]]) 533 | fs.append([vl[1], vl[2], vl[6], vl[5]]) 534 | fs.append([vl[2], vl[3], vl[7], vl[6]]) 535 | fs.append([vl[3], vl[0], vl[4], vl[7]]) 536 | blockcentre = block_centres[bid] 537 | for f in fs: 538 | fid, tmp = findFace(faces_as_list_of_nodes, f) 539 | normal = face_info[fid]['normal'] 540 | facecentre = face_info[fid]['centre'] 541 | direction = normal.dot((blockcentre-facecentre)) 542 | if direction >= 0.: 543 | face_info[fid]['pos'].append(bid) 544 | else: 545 | face_info[fid]['neg'].append(bid) 546 | 547 | for f in face_info: # Not more than two blocks on each side of a face. If a block scores too high in 'offences' it will be ruled out 548 | if len(face_info[f]['pos'])>1: 549 | for bid in face_info[f]['pos']: 550 | offences[bid] += 1 551 | if len(face_info[f]['neg'])>1: 552 | for bid in face_info[f]['neg']: 553 | offences[bid] += 1 554 | 555 | block_print_out = [] 556 | for bid, vl in enumerate(formalBlocks): 557 | if offences[bid] <= 3 and not all( v in disabled for v in vl ): 558 | block_print_out.append(vl) 559 | i_edges = [edge(vl[0],vl[1]), edge(vl[2],vl[3]), edge(vl[4],vl[5]), edge(vl[6],vl[7])] 560 | j_edges = [edge(vl[1],vl[2]), edge(vl[3],vl[0]), edge(vl[5],vl[6]), edge(vl[7],vl[4])] 561 | k_edges = [edge(vl[0],vl[4]), edge(vl[1],vl[5]), edge(vl[2],vl[6]), edge(vl[3],vl[7])] 562 | dependent_edges.append(i_edges) #these 4 edges have the same resolution 563 | dependent_edges.append(j_edges) #these 4 edges have the same resolution 564 | dependent_edges.append(k_edges) #these 4 edges have the same resolution 565 | for e in range(4): 566 | if not i_edges[e] in all_edges: 567 | all_edges.append(i_edges[e]) 568 | if not j_edges[e] in all_edges: 569 | all_edges.append(j_edges[e]) 570 | if not k_edges[e] in all_edges: 571 | all_edges.append(k_edges[e]) 572 | else: # Dont let non-allowed blocks to stop definition of patch names 573 | for f in face_info: 574 | if bid in face_info[f]['pos']: 575 | ind = face_info[f]['pos'].index(bid) 576 | face_info[f]['pos'].pop(ind) 577 | if bid in face_info[f]['neg']: 578 | ind = face_info[f]['neg'].index(bid) 579 | face_info[f]['neg'].pop(ind) 580 | 581 | still_coupling = True 582 | while still_coupling: 583 | still_coupling = couple_edges(dependent_edges) 584 | 585 | for es, edgeSet in enumerate(dependent_edges): # remove duplicates in lists 586 | dependent_edges[es] = removedup(edgeSet) 587 | 588 | return logFile, block_print_out, dependent_edges, face_info, all_edges, faces_as_list_of_nodes 589 | 590 | def smootherProfile(): 591 | return [-7.438494264988549e-15, 0.008605801693457149, 0.01842475807111854, 0.029612300648478973, 0.04233898400088343, 0.05679046185429426, 0.07316690916267587, 0.09168171542424897, 0.11255925232965769, 0.13603150492164162, 0.16233335611953192, 0.19169633764727423, 0.22434071509985654, 0.26046587000768784, 0.3002390841616158, 0.34378302306103725, 0.3911624496237259, 0.44237095781465174, 0.4973187660157523, 0.5558228055972574, 0.6176004265387395, 0.6822679660953321, 0.7493451518442462, 0.8182658327045534, 0.8883948904481273, 0.9590504652620432, 1.0295299509754288, 1.0991377055224336, 1.1672121827956488, 1.233150273303395, 1.2964270258686001, 1.3566095317190248, 1.413364466852664, 1.466459481809475, 1.51575919532818, 1.5612169266010856, 1.6028634731905456, 1.640794230426153, 1.6751558000542404, 1.7061330065249791, 1.7339369799788438, 1.7587947154353591, 1.780940303263625, 1.8006078588113952, 1.8180260609303134, 1.8334141352670894, 1.8469790804055504, 1.858913924091119, 1.8693968042865756, 1.8785906885314005, 1.8785906885314005, 1.8693968042865756, 1.8589139240911188, 1.8469790804055504, 1.8334141352670896, 1.8180260609303134, 1.8006078588113952, 1.7809403032636248, 1.7587947154353591, 1.733936979978844, 1.7061330065249791, 1.6751558000542404, 1.6407942304261525, 1.6028634731905456, 1.5612169266010856, 1.51575919532818, 1.4664594818094752, 1.4133644668526637, 1.3566095317190248, 1.2964270258685997, 1.233150273303395, 1.167212182795649, 1.0991377055224336, 1.0295299509754288, 0.9590504652620429, 0.8883948904481273, 0.8182658327045538, 0.7493451518442462, 0.6822679660953324, 0.6176004265387393, 0.5558228055972576, 0.49731876601575203, 0.44237095781465174, 0.39116244962372615, 0.3437830230610369, 0.3002390841616158, 0.2604658700076876, 0.22434071509985654, 0.19169633764727423, 0.16233335611953192, 0.13603150492164162, 0.11255925232965747, 0.09168171542424897, 0.07316690916267576, 0.05679046185429426, 0.04233898400088343, 0.029612300648478973, 0.01842475807111854, 0.008605801693457149, -7.438494264988549e-15] 592 | 593 | def foamHeader(): 594 | return """/*--------------------------------*- C++ -*----------------------------------*/ 595 | 596 | // File was generated by SwiftBlock, a Blender 3D addon. 597 | 598 | FoamFile 599 | { 600 | version 2.0; 601 | format ascii; 602 | class dictionary; 603 | object blockMeshDict; 604 | } 605 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 606 | """ 607 | 608 | 609 | def foamFileEnd(): 610 | return """); 611 | 612 | mergePatchPairs 613 | ( 614 | ); 615 | 616 | // ************************************************************************* // 617 | """ 618 | 619 | 620 | 621 | 622 | 623 | --------------------------------------------------------------------------------