├── README.md ├── __init__.py ├── blender_utils.py └── utils.py /README.md: -------------------------------------------------------------------------------- 1 | swiftSnap 2 | ========= 3 | Blender 3D addon for creating snappyHexMeshDict and associated files for OpenFOAM's snappyHexMesh application. Find more info on openfoamwiki.net 4 | 5 | This version is compatible with Blender 2.70 - 2.72 6 | -------------------------------------------------------------------------------- /__init__.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 | bl_info = { 20 | "name": "SwiftSnap", 21 | "author": "Karl-Johan Nogenmyr", 22 | "version": (0, 1), 23 | "blender": (2, 6, 6), 24 | "api": 35622, 25 | "location": "Tool Shelf", 26 | "description": "Writes snappyHexMeshDict from a Blender object", 27 | "warning": "not much tested yet", 28 | "wiki_url": "http://openfoamwiki.net/index.php/SwiftSnap", 29 | "tracker_url": "", 30 | "support": 'COMMUNITY', 31 | "category": "OpenFOAM"} 32 | 33 | #---------------------------------------------------------- 34 | # File scene_props.py 35 | #---------------------------------------------------------- 36 | import bpy 37 | import mathutils 38 | from bpy.props import * 39 | 40 | def shmpatchColor(patch_no): 41 | 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)] 42 | return color[patch_no % len(color)] 43 | 44 | def writeeMeshFile(path,verts,edges,level): 45 | filename = 'level%s.eMesh'%level 46 | f = open(path+'/' + filename, 'w') 47 | fobj = open(path+'/level%s.obj'%level, 'w') 48 | 49 | f.write(utils.foamHeader('featureEdgeMesh','points')) 50 | f.write('(\n') 51 | for v in verts: 52 | f.write('({} {} {})\n'.format(*v)) 53 | fobj.write('v {} {} {}\n'.format(*v)) 54 | f.write(')\n\n') 55 | f.write('(\n') 56 | for e in edges: 57 | f.write('({} {})\n'.format(*e)) 58 | fobj.write('l {} {}\n'.format(e[0]+1,e[1]+1)) 59 | f.write(')\n') 60 | f.close() 61 | return filename 62 | 63 | 64 | def initshmProperties(): 65 | 66 | bpy.types.Scene.ctmFloat = FloatProperty( 67 | name = "convertToMeters", 68 | description = "Conversion factor: Blender coords to meter", 69 | default = 1.0, 70 | min = 0.) 71 | 72 | bpy.types.Scene.snap = BoolProperty( 73 | name = "Snap mesh", 74 | description = "Should snappyHexMesh do the snapping stage?", 75 | default = True) 76 | 77 | bpy.types.Scene.cast = BoolProperty( 78 | name = "Castellated mesh", 79 | description = "Should snappyHexMesh do a castellated mesh?", 80 | default = True) 81 | 82 | bpy.types.Scene.lays = BoolProperty( 83 | name = "Add layers", 84 | description = "Should snappyHexMesh add boundary layer cells?", 85 | default = False) 86 | 87 | bpy.types.Scene.patchMinLevel = IntProperty( 88 | name = "Min level", 89 | description = "The minimum refinement level at this patch", 90 | min = 0) 91 | 92 | bpy.types.Scene.patchMaxLevel = IntProperty( 93 | name = "Max level", 94 | description = "The maximum refinement level at this patch", 95 | min = 0) 96 | 97 | bpy.types.Scene.patchLayers = IntProperty( 98 | name = "Layers", 99 | description = "Number of layers to grow at patch", 100 | min = 0) 101 | 102 | bpy.types.Scene.impFeat = BoolProperty( 103 | name = "Implicit features", 104 | description = "Should snappyHexMesh find features by itself?", 105 | default = False) 106 | 107 | bpy.types.Scene.featLevel = IntProperty( 108 | name = "Feat. level", 109 | description = "Selects feature line refinement level", 110 | min = 0) 111 | 112 | bpy.types.Scene.refineLevel = IntProperty( 113 | name = "Level", 114 | description = "Sets region's refinement level", 115 | min = 0) 116 | 117 | bpy.types.Scene.refineDist = FloatProperty( 118 | name = "Distance", 119 | description = "Sets region's refinement distance", 120 | min = 0.) 121 | 122 | bpy.types.Scene.refineInside = BoolProperty( 123 | name = "Refine inside object", 124 | description = "Should refinement be inside the object?", 125 | default = True) 126 | 127 | bpy.types.Scene.shmpatchName = StringProperty( 128 | name = "Name", 129 | description = "Specify name of patch (max 31 chars)", 130 | default = "defaultName") 131 | 132 | bpy.types.Scene.featAngle = FloatProperty( 133 | name = "featureAngle", 134 | description = "Feature angle", 135 | default = 30.0, 136 | min = 0.) 137 | 138 | bpy.types.Scene.resFloat = FloatProperty( 139 | name = "resolution", 140 | description = "The spatial resolution of base mesh in meter", 141 | default = 1.0, 142 | min = 0) 143 | 144 | bpy.types.Scene.makeBMD = BoolProperty( 145 | name = "Make base mesh", 146 | description = "Should a blockMeshDict be auto-generated?", 147 | default = False) 148 | 149 | bpy.types.Scene.refregName = StringProperty( 150 | name = "Refine region", 151 | description = "Name of object to use as refinement region", 152 | default = '') 153 | 154 | bpy.types.Scene.bcTypeEnum = EnumProperty( # only types relevant for sHM listed. Know any more? contact me... 155 | items = [('wall', 'wall', 'Defines the patch as wall'), 156 | ('patch', 'patch', 'Defines the patch as generic patch'), 157 | ('cyclicAMI', 'cyclicAMI', 'Defines the patch as cyclicAMI') 158 | ], 159 | name = "Patch type") 160 | 161 | return 162 | 163 | 164 | # 165 | # Menu in UI region 166 | # 167 | class RSUIPanel(bpy.types.Panel): 168 | bl_label = "SwiftSnap settings" 169 | bl_space_type = 'PROPERTIES' 170 | bl_region_type = 'WINDOW' 171 | bl_context = "object" 172 | 173 | def draw(self, context): 174 | layout = self.layout 175 | scn = context.scene 176 | obj = context.active_object 177 | settings = context.tool_settings 178 | try: 179 | obj['swiftsnap'] 180 | except: 181 | layout.operator("enable.swiftsnap") 182 | else: 183 | layout.operator("write.shmfile") 184 | # layout.prop(scn, 'cast') # Always do castalleted mesh 185 | layout.prop(scn, 'ctmFloat') 186 | if scn.makeBMD: 187 | split = layout.split() 188 | col = split.column() 189 | col.prop(scn, 'makeBMD') 190 | col = split.column() 191 | col.prop(scn, 'resFloat') 192 | else: 193 | layout.prop(scn, 'makeBMD') 194 | split = layout.split() 195 | col = split.column() 196 | col.prop(scn, 'snap') 197 | col = split.column() 198 | col.prop(scn, 'lays') 199 | layout.operator("set.locationinmesh", text="Set locationInMesh") 200 | box = layout.box() 201 | split = box.split() 202 | col = split.column() 203 | col.label(text='Feature settings') 204 | col = split.column() 205 | col.prop(scn, 'impFeat') 206 | col = box.column(align=True) 207 | col.operator("sel.nonmani") 208 | col.prop(scn, 'featAngle') 209 | col.operator("sel.feature") 210 | col.prop(scn, 'featLevel') 211 | col.operator('mark.feature',text= "Mark as Level %s"%scn.featLevel) 212 | col.operator('unmark.feature') 213 | if obj['featLevels'].items(): 214 | col.label(text='Defined features, click to select') 215 | row = col.row(align=True) 216 | for level in obj['featLevels']: 217 | row.operator('show.feature',text=level, emboss=False).whichLevel = int(level) 218 | 219 | # col.prop(obj.data,'show_edge_sharp',text="Show marked feats.") # This thing can make feature lines shine blue! 220 | box = layout.box() 221 | box.label(text="Patch settings") 222 | box.prop(scn, 'shmpatchName') 223 | box.prop(scn, 'bcTypeEnum') 224 | row = box.row(align=True) 225 | row.prop(scn, 'patchMinLevel') 226 | row.prop(scn, 'patchMaxLevel') 227 | row = box.row(align=False) 228 | if scn.lays: 229 | row.prop(scn, 'patchLayers') 230 | row.operator("set.shmpatchname") 231 | if scn.lays: 232 | box.label(text="Color, min, max, name, type, layers") 233 | else: 234 | box.label(text="Color, min, max, name, type") 235 | for m in obj.data.materials: 236 | try: 237 | textstr = '{}, {}, {}, {}'.format(m['minLevel'],m['maxLevel'], m.name, m['patchType']) 238 | lays = str(m['patchLayers']) 239 | if scn.lays: 240 | textstr += ', ' + lays 241 | split = box.split(percentage=0.2, align=False) 242 | col = split.column() 243 | col.prop(m, "diffuse_color", text="") 244 | col = split.column() 245 | col.operator("set.shmgetpatch", text=textstr, emboss=False).whichPatch = m.name 246 | except: 247 | pass 248 | box = layout.box() 249 | box.label(text="Refinement settings") 250 | box.prop(scn, 'refregName') 251 | row = box.row(align=True) 252 | row.prop(scn, 'refineLevel') 253 | row.prop(scn, 'refineDist') 254 | box.prop(scn,"refineInside") 255 | col = box.column() 256 | col.enabled = False 257 | if scn.refregName in bpy.data.objects: 258 | col.enabled = True 259 | col.operator("set.refreg") 260 | box.row(align=False) 261 | try: 262 | for refreg in obj['refreg']: 263 | if obj['refreg'][refreg]['inside']: 264 | mode = 'inside' 265 | else: 266 | mode = 'outside' 267 | textstr = "Refine {1:g} level {2:g} m {3:} {0:}".format(refreg, obj['refreg'][refreg]['level'], obj['refreg'][refreg]['dist'],mode) 268 | box.operator("remove.refine", text=textstr, emboss=False).whichObj = refreg 269 | except: 270 | pass 271 | 272 | 273 | class OBJECT_OT_SetRefRegObj(bpy.types.Operator): 274 | '''Set given object as a refinment region''' 275 | bl_idname = "set.refreg" 276 | bl_label = "Set Refinement Region" 277 | 278 | def execute(self, context): 279 | obj = context.active_object 280 | scn = context.scene 281 | name = scn.refregName 282 | try: 283 | bpy.data.objects[name] 284 | try: 285 | obj['refreg'] 286 | except: 287 | obj['refreg'] = {} 288 | if not name in obj['refreg']: 289 | obj['refreg'][name] = {} 290 | obj['refreg'][name]['level'] = scn.refineLevel 291 | obj['refreg'][name]['dist'] = scn.refineDist 292 | obj['refreg'][name]['inside'] = scn.refineInside 293 | except: 294 | print("Object %s not found!"%name) 295 | 296 | return {'FINISHED'} 297 | 298 | class OBJECT_OT_UnsetRefRegObj(bpy.types.Operator): 299 | '''Click to remove this refinement''' 300 | bl_idname = "remove.refine" 301 | bl_label = "Defined refinement region. Click to remove." 302 | 303 | whichObj = StringProperty() 304 | 305 | def execute(self, context): 306 | obj = context.active_object 307 | obj['refreg'].pop(self.whichObj) 308 | return {'FINISHED'} 309 | 310 | class OBJECT_OT_SetInsidePoint(bpy.types.Operator): 311 | '''Set the locationInMesh coordinate using the 3D cursor''' 312 | bl_idname = "set.locationinmesh" 313 | bl_label = "Update locationInMesh. ESC to cancel" 314 | 315 | def execute(self, context): 316 | obj = context.active_object 317 | obj['locinmesh'] = context.scene.cursor_location.copy() 318 | return {'FINISHED'} 319 | 320 | def invoke(self, context, event): 321 | context.window_manager.invoke_props_dialog(self, width=320) 322 | return {'RUNNING_MODAL'} 323 | 324 | def draw(self, context): 325 | scn = context.scene 326 | self.layout.label("Let snappyHexMesh mesh the volume containing the point:") 327 | self.layout.prop(scn, "cursor_location", text='') 328 | 329 | class OBJECT_OT_Nonmani(bpy.types.Operator): 330 | '''Finds and selects non-manifold edges''' 331 | bl_idname = "sel.nonmani" 332 | bl_label = "Detect Non-Manifold" 333 | 334 | def execute(self, context): 335 | obj = context.active_object 336 | bpy.ops.object.mode_set(mode='OBJECT') 337 | bpy.ops.object.mode_set(mode='EDIT') 338 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(False,True,False)") 339 | bpy.ops.mesh.select_all(action='DESELECT') 340 | bpy.ops.mesh.select_non_manifold() 341 | return {'FINISHED'} 342 | 343 | class OBJECT_OT_FeatureSel(bpy.types.Operator): 344 | '''Finds and selects feature edges''' 345 | bl_idname = "sel.feature" 346 | bl_label = "Detect Features" 347 | 348 | def execute(self, context): 349 | obj = context.active_object 350 | scn = context.scene 351 | bpy.ops.object.mode_set(mode='OBJECT') 352 | bpy.ops.object.mode_set(mode='EDIT') 353 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(False,True,False)") 354 | bpy.ops.mesh.select_all(action='DESELECT') 355 | bpy.ops.mesh.edges_select_sharp(sharpness=scn.featAngle*3.141592653589793/180) 356 | return {'FINISHED'} 357 | 358 | class OBJECT_OT_FeatureMark(bpy.types.Operator): 359 | '''Marks selected edges as feature lines''' 360 | bl_idname = "mark.feature" 361 | bl_label = "Mark selected edges as feature lines" 362 | 363 | def execute(self, context): 364 | obj = context.active_object 365 | scn = context.scene 366 | bpy.ops.object.mode_set(mode='OBJECT') 367 | for e in obj.data.edges: 368 | if e.select: 369 | e.use_edge_sharp = True 370 | e.bevel_weight = 0.01*scn.featLevel # Store level info in bevel_weight (which is a float in range [0,1]) 371 | 372 | obj['featLevels'] = {} # Reset the info on present feature edge levels 373 | for e in obj.data.edges: 374 | if e.use_edge_sharp: 375 | obj['featLevels'][str(round(100*e.bevel_weight))] = 1 376 | bpy.ops.object.mode_set(mode='EDIT') 377 | return {'FINISHED'} 378 | 379 | class OBJECT_OT_FeatureUnmark(bpy.types.Operator): 380 | '''Unmarks selected edges as feature lines''' 381 | bl_idname = "unmark.feature" 382 | bl_label = "Unmark Selected" 383 | 384 | def execute(self, context): 385 | obj = context.active_object 386 | bpy.ops.object.mode_set(mode='OBJECT') 387 | for e in obj.data.edges: 388 | if e.select: 389 | e.use_edge_sharp = False 390 | e.bevel_weight = 0.0 391 | 392 | obj['featLevels'] = {} # Reset the info on present feature edge levels 393 | for e in obj.data.edges: 394 | if e.use_edge_sharp: 395 | obj['featLevels'][str(round(100*e.bevel_weight))] = 1 396 | 397 | bpy.ops.object.mode_set(mode='EDIT') 398 | return {'FINISHED'} 399 | 400 | class OBJECT_OT_FeatureShow(bpy.types.Operator): 401 | '''Show previously set feature edges''' 402 | bl_idname = "show.feature" 403 | bl_label = "Show previously set feature edges" 404 | 405 | whichLevel = IntProperty() 406 | 407 | def execute(self, context): 408 | obj = context.active_object 409 | scn = context.scene 410 | bpy.ops.object.mode_set(mode='EDIT') 411 | bpy.ops.mesh.select_all(action='DESELECT') 412 | bpy.ops.object.mode_set(mode='OBJECT') 413 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(False,True,False)") 414 | 415 | for e in obj.data.edges: 416 | e.select = False 417 | if e.use_edge_sharp and round(100*e.bevel_weight) == self.whichLevel: 418 | e.select = True 419 | bpy.ops.object.mode_set(mode='EDIT') 420 | scn.featLevel = self.whichLevel 421 | return {'FINISHED'} 422 | 423 | class OBJECT_OT_shmEnable(bpy.types.Operator): 424 | '''Enables SwiftSnap for the active object''' 425 | bl_idname = "enable.swiftsnap" 426 | bl_label = "Enable SwiftSnap" 427 | # initializes an object 428 | def execute(self, context): 429 | 430 | bpy.ops.object.mode_set(mode='OBJECT') 431 | obj = context.active_object 432 | scn = context.scene 433 | obj.data.use_customdata_edge_crease = True 434 | obj.data.use_customdata_edge_bevel = True 435 | 436 | obj['swiftsnap'] = True 437 | obj['featLevels'] = {} 438 | bpy.ops.object.mode_set(mode='OBJECT') 439 | bpy.ops.object.material_slot_remove() 440 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(False,True,False)") 441 | for e in obj.data.edges: 442 | e.use_edge_sharp=False 443 | e.bevel_weight = 0. 444 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(False,False,True)") 445 | for f in obj.data.polygons: 446 | f.select=True 447 | try: 448 | mat = bpy.data.materials['defaultName'] 449 | patchindex = list(obj.data.materials).index(mat) 450 | obj.active_material_index = patchindex 451 | except: 452 | mat = bpy.data.materials.new('defaultName') 453 | mat.diffuse_color = (0.5,0.5,0.5) 454 | bpy.ops.object.material_slot_add() 455 | obj.material_slots[-1].material = mat 456 | mat['minLevel'] = scn.patchMinLevel 457 | mat['maxLevel'] = scn.patchMaxLevel 458 | mat['patchLayers'] = scn.patchLayers 459 | mat['patchType'] = scn.bcTypeEnum 460 | bpy.ops.object.editmode_toggle() 461 | bpy.ops.object.material_slot_assign() 462 | bpy.ops.object.editmode_toggle() 463 | for v in obj.data.vertices: 464 | v.select=False 465 | bpy.ops.object.editmode_toggle() 466 | return{'FINISHED'} 467 | 468 | class OBJECT_OT_shmSetPatchName(bpy.types.Operator): 469 | '''Set the given name to the selected faces''' 470 | bl_idname = "set.shmpatchname" 471 | bl_label = "Set Patch" 472 | 473 | def execute(self, context): 474 | scn = context.scene 475 | obj = context.active_object 476 | bpy.ops.object.mode_set(mode='OBJECT') 477 | namestr = scn.shmpatchName 478 | namestr = namestr.strip() 479 | namestr = namestr.replace(' ', '_') 480 | try: 481 | mat = bpy.data.materials[namestr] 482 | patchindex = list(obj.data.materials).index(mat) 483 | obj.active_material_index = patchindex 484 | except: # add a new patchname (as a blender material, as such face props are conserved during mesh mods) 485 | mat = bpy.data.materials.new(namestr) 486 | mat.diffuse_color = shmpatchColor(len(obj.data.materials)-1) 487 | bpy.ops.object.material_slot_add() 488 | obj.material_slots[-1].material = mat 489 | mat['minLevel'] = scn.patchMinLevel 490 | scn.patchMaxLevel = max(scn.patchMaxLevel, scn.patchMinLevel) 491 | mat['maxLevel'] = scn.patchMaxLevel 492 | mat['patchLayers'] = scn.patchLayers 493 | mat['patchType'] = scn.bcTypeEnum 494 | bpy.ops.object.editmode_toggle() 495 | bpy.ops.object.material_slot_assign() 496 | return {'FINISHED'} 497 | 498 | class OBJECT_OT_shmGetPatch(bpy.types.Operator): 499 | '''Click to select faces belonging to this patch''' 500 | bl_idname = "set.shmgetpatch" 501 | bl_label = "Get Patch" 502 | 503 | whichPatch = StringProperty() 504 | 505 | def execute(self, context): 506 | scn = context.scene 507 | obj = context.active_object 508 | bpy.ops.object.mode_set(mode='EDIT') 509 | bpy.ops.mesh.select_all(action='DESELECT') 510 | bpy.ops.object.mode_set(mode='OBJECT') 511 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(False,False,True)") 512 | for f in obj.data.polygons: 513 | f.select = False 514 | mat = bpy.data.materials[self.whichPatch] 515 | patchindex = list(obj.data.materials).index(mat) 516 | obj.active_material_index = patchindex 517 | bpy.ops.object.editmode_toggle() 518 | bpy.ops.object.material_slot_select() 519 | scn.patchMinLevel = mat['minLevel'] 520 | scn.patchMaxLevel =mat['maxLevel'] 521 | scn.patchLayers = mat['patchLayers'] 522 | scn.bcTypeEnum = mat['patchType'] 523 | scn.shmpatchName = self.whichPatch 524 | return {'FINISHED'} 525 | 526 | class OBJECT_OT_writeSHM(bpy.types.Operator): 527 | '''Write snappyHexMeshDict and associated files for selected object''' 528 | bl_idname = "write.shmfile" 529 | bl_label = "Write" 530 | 531 | if "bpy" in locals(): 532 | import imp 533 | if "utils" in locals(): 534 | imp.reload(utils) 535 | if "blender_utils" in locals(): 536 | imp.reload(blender_utils) 537 | 538 | 539 | distributeFiles = BoolProperty(name="Distribute", 540 | description="Put triSurface dir in ../constant", 541 | default=False) 542 | 543 | surfaceFileType = EnumProperty( 544 | items = [('stl', 'stl', 'Export geometry as triangulated surfaces in ASCII stl format'), 545 | ('obj', 'obj', 'Export geometry as Wavefront obj files') 546 | ], 547 | name = "Surface file type", 548 | default = "obj") 549 | 550 | filepath = StringProperty( 551 | name="File Path", 552 | description="Filepath used for exporting the file", 553 | maxlen=1024, 554 | subtype='FILE_PATH', 555 | ) 556 | check_existing = BoolProperty( 557 | name="Check Existing", 558 | description="Check and warn on overwriting existing files", 559 | default=True, 560 | options={'HIDDEN'}, 561 | ) 562 | 563 | def invoke(self, context, event): 564 | try: 565 | self.filepath = context.active_object['path'] 566 | self.distributeFiles = context.active_object['distributeFiles'] 567 | except: 568 | self.filepath = 'snappyHexMeshDict' 569 | bpy.context.window_manager.fileselect_add(self) 570 | return {'RUNNING_MODAL'} 571 | 572 | def execute(self, context): 573 | from . import utils 574 | import imp 575 | imp.reload(utils) 576 | from . import blender_utils 577 | from io_scene_obj import export_obj 578 | import os 579 | scn = context.scene 580 | surffiles = [] 581 | eMeshfiles = [] 582 | refinefiles = [] 583 | edgeMesh = {} 584 | sc = scn.ctmFloat 585 | 586 | path = os.path.dirname(self.filepath) 587 | if self.distributeFiles: 588 | pathtrisurface = os.path.join(path,'..','constant','triSurface') 589 | else: 590 | pathtrisurface = os.path.join(path,'triSurface') 591 | if not os.path.exists(pathtrisurface): 592 | os.makedirs(pathtrisurface) 593 | 594 | geoobj = context.active_object 595 | geoname = geoobj.name 596 | geoobj['path'] = self.filepath 597 | geoobj['distributeFiles'] = self.distributeFiles 598 | 599 | bpy.ops.object.mode_set(mode='OBJECT') 600 | bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN') 601 | for ob in bpy.data.objects: 602 | ob.select = False 603 | 604 | context.scene.cursor_location = geoobj.location 605 | try: 606 | for refreg in geoobj['refreg']: 607 | refobj = bpy.data.objects[refreg] 608 | refobj.select = True 609 | hideStatus = refobj.hide 610 | refobj.hide = False 611 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR') 612 | filename = refreg + "." + self.surfaceFileType 613 | 614 | refinefiles.append([filename,geoobj['refreg'][refreg]['level'], geoobj['refreg'][refreg]['dist'], geoobj['refreg'][refreg]['inside']]) 615 | filenameandpath = os.path.join(pathtrisurface, filename) 616 | bpy.ops.object.duplicate(linked=False, mode='INIT') 617 | bpy.ops.transform.resize(value=(sc,sc,sc)) 618 | bpy.data.objects[refreg].select = True 619 | refobj.select = False 620 | print() 621 | if self.surfaceFileType == 'stl': 622 | bpy.ops.export_mesh.stl(filepath=filenameandpath, check_existing=False, ascii=True, use_mesh_modifiers=True) 623 | elif self.surfaceFileType == 'obj': 624 | export_obj.save(bpy.ops, bpy.context, filepath=filenameandpath, use_materials=False, use_selection=True) 625 | bpy.ops.object.delete() 626 | refobj.select = False 627 | refobj.hide = hideStatus 628 | except: 629 | pass 630 | 631 | obj = bpy.data.objects[geoname] 632 | obj.select = True 633 | 634 | try: 635 | cent = obj.location 636 | locinmesh = (mathutils.Vector(geoobj['locinmesh'])-cent)*sc + cent 637 | except: 638 | locinmesh = (0,0,0) 639 | 640 | bpy.ops.object.duplicate(linked=False, mode='INIT') 641 | bpy.ops.transform.resize(value=(sc,sc,sc)) 642 | obj = context.active_object 643 | obj.name = 'shmCopyWriteOut' 644 | bpy.ops.object.duplicate(linked=False, mode='INIT') 645 | obj = context.active_object 646 | obj.name = 'edgemesh' 647 | levelToEdgeMap = {} 648 | 649 | bpy.ops.object.mode_set(mode='EDIT') 650 | bpy.ops.mesh.select_all(action='DESELECT') 651 | bpy.ops.object.mode_set(mode='OBJECT') 652 | 653 | for e in obj.data.edges: 654 | if e.use_edge_sharp: 655 | e.select = False 656 | level = round(e.bevel_weight*100) 657 | if not level in levelToEdgeMap: 658 | levelToEdgeMap[level] = [] 659 | levelToEdgeMap[level].append(e.index) 660 | 661 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(False,True,False)") 662 | 663 | for level in levelToEdgeMap: 664 | obj.select = True 665 | for e in levelToEdgeMap[level]: 666 | obj.data.edges[e].select = True 667 | bpy.ops.object.mode_set(mode='EDIT') 668 | bpy.ops.mesh.duplicate() 669 | bpy.ops.object.mode_set(mode='OBJECT') 670 | bpy.ops.object.mode_set(mode='EDIT') 671 | bpy.ops.mesh.separate(type='SELECTED') 672 | bpy.ops.object.mode_set(mode='OBJECT') 673 | eobj = bpy.data.objects['edgemesh.001'] 674 | verts = list(blender_utils.vertices_from_mesh(eobj)) 675 | edges = list(blender_utils.edges_from_mesh(eobj)) 676 | eMeshfiles.append(writeeMeshFile(pathtrisurface,verts,edges,level)) 677 | obj.select = False 678 | eobj.select = True 679 | bpy.ops.object.delete() 680 | obj = bpy.data.objects['edgemesh'] 681 | for e in levelToEdgeMap[level]: 682 | obj.data.edges[e].select = False 683 | 684 | 685 | obj = bpy.data.objects['shmCopyWriteOut'] 686 | obj.select = True 687 | bpy.context.scene.objects.active = obj 688 | for v in obj.data.vertices: 689 | v.select=True 690 | bpy.ops.object.mode_set(mode='EDIT') 691 | bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(False,False,True)") 692 | bpy.ops.mesh.separate(type='MATERIAL') 693 | bpy.ops.object.mode_set(mode='OBJECT') 694 | for ob in bpy.data.objects: 695 | ob.select = False 696 | 697 | N = 0 698 | for ob in bpy.data.objects: 699 | if 'shmCopyWriteOut' in ob.name and len(ob.data.polygons) > 0: 700 | N = N + 1 701 | ob.select = True 702 | matID = ob.data.polygons[0].material_index 703 | mat = ob.data.materials[matID] 704 | filename = mat.name + '.' + self.surfaceFileType 705 | surffiles.append([filename,mat['minLevel'], mat['maxLevel'], mat['patchLayers'], mat['patchType']]) 706 | filenameandpath = os.path.join(pathtrisurface, filename) 707 | if self.surfaceFileType == 'stl': 708 | bpy.ops.export_mesh.stl(filepath=filenameandpath, check_existing=False, ascii=True, use_mesh_modifiers=True) 709 | elif self.surfaceFileType == 'obj': 710 | export_obj.save(bpy.ops, bpy.context, filepath=filenameandpath, use_materials=False, use_selection=True) 711 | ob.select = False 712 | 713 | # EnGrid export start. Code from io_export_engrid.py written by Oliver Gloth (enGits GmbH) 714 | split_quads = True 715 | out = open(os.path.join(path, "engrid.begc"), "w") 716 | out.write('%d\n' % N) 717 | node_offset = 0 718 | for ob in bpy.data.objects: 719 | if 'shmCopyWriteOut' in ob.name and len(ob.data.polygons) > 0: 720 | ob.select = True 721 | matID = ob.data.polygons[0].material_index 722 | mat = obj.data.materials[matID] 723 | out.write(mat.name) 724 | out.write('\n') 725 | 726 | for ob in bpy.data.objects: 727 | if 'shmCopyWriteOut' in ob.name and len(ob.data.polygons) > 0: 728 | mesh = ob.data 729 | mesh.update(calc_tessface=True) 730 | M = ob.matrix_world 731 | mesh.transform(M) 732 | faces = mesh.tessfaces 733 | nodes = mesh.vertices 734 | out.write('%d' % len(nodes)) 735 | if split_quads: 736 | N = len(faces) 737 | for f in faces: 738 | if len(f.vertices) == 4: 739 | N = N + 1 740 | out.write(' %d\n' % N) 741 | else: 742 | out.write(' %d\n' % len(faces)) 743 | for n in nodes: 744 | out.write("%e " % n.co[0]) 745 | out.write("%e " % n.co[1]) 746 | out.write("%e\n" % n.co[2]) 747 | for f in faces: 748 | N = len(f.vertices) 749 | if split_quads: 750 | if N != 4: 751 | out.write("%d" % len(f.vertices)) 752 | for v in f.vertices: 753 | out.write(' %d' % (v + node_offset)) 754 | out.write('\n') 755 | else: 756 | out.write('3 %d %d %d\n' % (f.vertices[0] + node_offset, f.vertices[1] + node_offset, f.vertices[2] + node_offset)) 757 | out.write('3 %d %d %d\n' % (f.vertices[2] + node_offset, f.vertices[3] + node_offset, f.vertices[0] + node_offset)) 758 | else: 759 | out.write("%d" % len(f.vertices)) 760 | for v in f.vertices: 761 | out.write(' %d' % (v + node_offset)) 762 | out.write('\n') 763 | node_offset = node_offset + len(nodes) 764 | M.invert() 765 | mesh.transform(M) 766 | out.flush() 767 | out.close() 768 | # EnGrid export end 769 | 770 | for ob in bpy.data.objects: 771 | if 'shmCopyWriteOut' in ob.name or 'edgemesh' in ob.name: 772 | ob.select = True 773 | bpy.ops.object.delete() 774 | implicit = [scn.impFeat, scn.featAngle] 775 | utils.write(self.filepath, obj, surffiles, refinefiles, eMeshfiles, scn.cast, scn.snap, scn.lays, locinmesh, implicit) 776 | obj = bpy.data.objects[geoname] 777 | obj.select = True 778 | bpy.context.scene.objects.active = obj 779 | 780 | if scn.makeBMD: 781 | verts = [] 782 | matrix = obj.matrix_world.copy() 783 | cent = obj.location 784 | for b in obj.bound_box: 785 | verts.append((matrix*mathutils.Vector(b)-cent)*1.02*sc+cent) 786 | res = scn.resFloat 787 | Nx = max(1,round((verts[4] - verts[5]).length / res)) 788 | Ny = max(1,round((verts[5] - verts[6]).length / res)) 789 | Nz = max(1,round((verts[0] - verts[4]).length / res)) 790 | if self.distributeFiles: 791 | path = os.path.join(path,'..','constant','polyMesh') 792 | if not os.path.exists(path): 793 | os.makedirs(path) 794 | utils.makeBMD(path, verts, (Nx,Ny,Nz)) 795 | return{'FINISHED'} 796 | 797 | initshmProperties() # Initialize 798 | 799 | def register(): 800 | bpy.utils.register_module(__name__) 801 | 802 | def unregister(): 803 | bpy.utils.unregister_module(__name__) 804 | 805 | -------------------------------------------------------------------------------- /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 | def faces_from_mesh(ob, apply_modifier=False, triangulate=True): 24 | ''' 25 | From an object, return a generator over a list of faces. 26 | 27 | Each faces is a list of his vertexes. Each vertex is a tuple of 28 | his coordinate. 29 | 30 | apply_modifier 31 | Apply the preview modifier to the returned liste 32 | 33 | triangulate 34 | Split the quad into two triangles 35 | ''' 36 | 37 | # get the modifiers 38 | try: 39 | mesh = ob.to_mesh(bpy.context.scene, apply_modifier, "PREVIEW") 40 | except RuntimeError: 41 | raise StopIteration 42 | 43 | mesh.transform(ob.matrix_world) 44 | 45 | if triangulate: 46 | # From a list of faces, return the face triangulated if needed. 47 | def iter_face_index(): 48 | for face in mesh.faces: 49 | vertices = face.vertices[:] 50 | if len(vertices) == 4: 51 | yield vertices[0], vertices[1], vertices[2] 52 | yield vertices[2], vertices[3], vertices[0] 53 | else: 54 | yield vertices 55 | else: 56 | def iter_face_index(): 57 | for face in mesh.faces: 58 | yield face.vertices[:] 59 | 60 | vertices = mesh.vertices 61 | 62 | for indexes in iter_face_index(): 63 | yield [vertices[index].co.copy() for index in indexes] 64 | 65 | bpy.data.meshes.remove(mesh) 66 | 67 | 68 | def vertices_from_mesh(ob): 69 | ''' 70 | ''' 71 | 72 | # get the modifiers 73 | try: 74 | mesh = ob.to_mesh(bpy.context.scene, False, "PREVIEW") 75 | except RuntimeError: 76 | raise StopIteration 77 | 78 | matrix = ob.matrix_world.copy() 79 | 80 | for v in mesh.vertices: 81 | yield (matrix*v.co) 82 | 83 | bpy.data.meshes.remove(mesh) 84 | 85 | def edges_from_mesh(ob): 86 | ''' 87 | ''' 88 | 89 | # get the modifiers 90 | try: 91 | mesh = ob.to_mesh(bpy.context.scene, False, "PREVIEW") 92 | except RuntimeError: 93 | raise StopIteration 94 | 95 | for e in mesh.edges: 96 | yield list(e.vertices) 97 | 98 | bpy.data.meshes.remove(mesh) 99 | -------------------------------------------------------------------------------- /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, obj, surffiles, refinestls,eMeshfiles, cast, snap, lays, locinmesh, implicit): 26 | 27 | shmd = open(filename,'w') 28 | shmd.write(foamHeader('dictionary','snappyHexMeshDict')) 29 | 30 | shmd.write("castellatedMesh %s;\n"%str(cast).lower()); 31 | shmd.write("snap %s;\n"%str(snap).lower()); 32 | shmd.write("addLayers %s;\n"%str(lays).lower()); 33 | 34 | shmd.write("\ngeometry\n{\n") 35 | for stl in surffiles: 36 | shmd.write(" " + stl[0] + "\n {\n type triSurfaceMesh;\n name %s;\n }\n"%stl[0][0:-4]) 37 | for ref in refinestls: 38 | shmd.write(" " + ref[0] + "\n {\n type triSurfaceMesh;\n name %s;\n }\n"%ref[0][0:-4]) 39 | shmd.write("};\n") 40 | 41 | shmd.write("\ncastellatedMeshControls\n{\n") 42 | 43 | shmd.write(" features\n (\n") 44 | for em in eMeshfiles: 45 | shmd.write(" {\n file \"" + em + "\";\n level %s;\n }\n"%em.strip('level.eMesh')) 46 | shmd.write(" );\n") 47 | 48 | shmd.write(" refinementSurfaces\n {\n") 49 | for stl in surffiles: 50 | pmin = stl[1] 51 | pmax = stl[2] 52 | ptype = stl[4] 53 | shmd.write(" %s\n {\n"%stl[0][0:-4]) 54 | shmd.write(" level ({} {});\n".format(pmin,pmax)) 55 | shmd.write(" patchInfo\n") 56 | shmd.write(" {\n") 57 | shmd.write(" type %s;\n"%ptype) 58 | shmd.write(" }\n") 59 | shmd.write(" }\n") 60 | shmd.write(" }\n") 61 | shmd.write(" refinementRegions\n {\n") 62 | for ref in refinestls: 63 | dist = ref[2] 64 | level = ref[1] 65 | if ref[3]: 66 | mode = 'inside' 67 | else: 68 | mode = 'outside' 69 | shmd.write(" %s\n {\n"%ref[0][0:-4]) 70 | shmd.write(" mode %s;\n"%mode) 71 | shmd.write(" levels (({} {}));\n".format(dist, level)) #snappy allows for more freedom here! 72 | shmd.write(" }\n") 73 | shmd.write(" }\n") 74 | shmd.write(" locationInMesh ({} {} {});\n".format(*locinmesh)) 75 | shmd.write(defaultSettingsCastMesh().format(implicit[1])) 76 | 77 | shmd.write("\naddLayersControls\n{\n layers\n {\n") 78 | for stl in surffiles: 79 | patchname = stl[0][0:-4] 80 | patchlayers = stl[3] 81 | shmd.write(" \"%s.*\"\n {\n "%patchname) 82 | shmd.write("nSurfaceLayers %s;\n }\n"%patchlayers) 83 | shmd.write(" }\n") 84 | 85 | shmd.write(defaultSettingsLayers()) 86 | 87 | shmd.write(defaultSettings().format(str(implicit[0]).lower())) 88 | shmd.write("// ************************************************************************* //") 89 | shmd.close() 90 | return 0 91 | 92 | def unique(seq): 93 | # order preserving 94 | checked = [] 95 | for e in seq: 96 | if e not in checked: 97 | checked.append(e) 98 | return checked 99 | 100 | def makeBMD(path,verts, N): 101 | filename = 'blockMeshDict' 102 | f = open(path+'/' + filename, 'w') 103 | 104 | f.write(foamHeader('dictionary','blockMeshDict')) 105 | f.write('convertToMeters 1;\n\nvertices\n(\n') 106 | for v in verts: 107 | f.write(' ({} {} {})\n'.format(*v)) 108 | f.write(');\n\n') 109 | f.write('blocks\n(\n') 110 | f.write(' hex (4 5 6 7 0 1 2 3) ({} {} {}) simpleGrading (1 1 1)\n'.format(*N)) 111 | f.write(');\n\nedges\n(\n);\nboundary\n(\n);\nmergePatchPairs\n(\n);\n\n') 112 | f.close() 113 | return filename 114 | 115 | 116 | def foamHeader(classtype, objecttype): 117 | return """/*--------------------------------*- C++ -*----------------------------------*/ 118 | 119 | // File was generated by SwiftSnap, a Blender 3D addon. 120 | 121 | FoamFile 122 | {{ 123 | version 2.0; 124 | format ascii; 125 | class {}; 126 | object {}; 127 | }} 128 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 129 | 130 | """.format(classtype,objecttype) 131 | 132 | 133 | def defaultSettings(): 134 | return """ 135 | snapControls 136 | {{ 137 | nSmoothPatch 3; 138 | tolerance 1.0; 139 | nSolveIter 300; 140 | nRelaxIter 5; 141 | nFeatureSnapIter 10; 142 | implicitFeatureSnap {}; 143 | explicitFeatureSnap true; 144 | multiRegionFeatureSnap false; 145 | }} 146 | 147 | meshQualityControls 148 | {{ 149 | maxNonOrtho 65; 150 | maxBoundarySkewness 20; 151 | maxInternalSkewness 4; 152 | maxConcave 80; 153 | minVol 1e-13; 154 | minTetQuality 1e-30; 155 | minArea -1; 156 | minTwist 0.05; 157 | minDeterminant 0.001; 158 | minFaceWeight 0.05; 159 | minVolRatio 0.01; 160 | minTriangleTwist -1; 161 | nSmoothScale 4; 162 | errorReduction 0.75; 163 | relaxed 164 | {{ 165 | maxNonOrtho 75; 166 | }} 167 | }} 168 | 169 | debug 0; 170 | mergeTolerance 1E-6; 171 | """ 172 | 173 | def defaultSettingsCastMesh(): 174 | return """ 175 | maxLocalCells 100000; 176 | maxGlobalCells 2000000; 177 | minRefinementCells 0; 178 | nCellsBetweenLevels 1; 179 | resolveFeatureAngle {}; 180 | allowFreeStandingZoneFaces true; 181 | }} 182 | """ 183 | 184 | def defaultSettingsLayers(): 185 | return """ 186 | relativeSizes true; 187 | expansionRatio 1.25; 188 | finalLayerThickness 0.3; 189 | minThickness 0.25; 190 | nGrow 0; 191 | featureAngle 30; 192 | slipFeatureAngle 30; 193 | nRelaxIter 5; 194 | nSmoothSurfaceNormals 1; 195 | nSmoothNormals 3; 196 | nSmoothThickness 10; 197 | maxFaceThicknessRatio 0.5; 198 | maxThicknessToMedialRatio 0.3; 199 | minMedianAxisAngle 90; 200 | nBufferCellsNoExtrude 0; 201 | nLayerIter 50; 202 | nRelaxedIter 20; 203 | nLayerIter 50; 204 | } 205 | """ 206 | 207 | 208 | 209 | 210 | --------------------------------------------------------------------------------