├── LICENSE ├── README.md ├── blendshell-dog-tex.jpg ├── blendshell.py └── blendshelltest-01.blend /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Oormi Creations 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BlendShell 2 | A Blender plugin for making hollow models suitable for 3D printing. 3 | It creates an inner shell within the model which is attached to the model making it hollow. The wall thickness can be specified. This brings down the cost of printing as less material is needed. It also cuts down the time for printing. Unlike the shell/solidify modifier, it does not deform the thin parts of the model. It is especially useful for sculpted organic models. 4 | You can control the accuracy and density of the shell. 5 | The addon has a utility to create drills and make holes in the hollow model. 6 | 7 | **Intro Video** 8 | 9 | https://www.youtube.com/watch?v=uURBobfasxk 10 | 11 | 12 | ![logo](https://oormi.in/software/software_images/blendshellthumb01.jpg) 13 | 14 | 15 | **Installation** 16 | 17 | Download the zip file from *Releases* page here. 18 | https://github.com/oormicreations/BlendShell/releases 19 | 20 | Extract it. In Blender, go to Edit->Preferences->Addons and click Install. Choose the blendshell.py file. Enable the Addon. You should see the UI in the vertical tabs in 3D view. (Visible only in *Object Mode*) 21 | Uninstall any previous versions before installing a new version. 22 | 23 | This addon is tested in Blender 2.80, 2.81, 2.82, 2.83.4, 2.9 and 2.92 on Linux Mint 19.2 24 | 25 | **Usage** 26 | 27 | * Create the seed shell by clicking the *Create Seed* button. 28 | * Adjust the size and position of the seed so that it sits inside your model. 29 | * Pick your model from the drop down box. 30 | * Click *Create Shell* button. 31 | * Click *Flip & Attach* button to finish. 32 | 33 | *It is recommended to test this addon on the provided blend file to get a feel of how the parameters work.* 34 | 35 | **Parameters** 36 | 37 | * *Seed Size* : Drag or enter the value to change the size of the seed. Ideally it should be big enough to fit inside the broadest part of the model. The seed should not come out of the model from any side. Also leave a margin that is somewhat greater than the wall thickness. 38 | *Warning: Clicking the Create Seed button or changing the seed size or divisions after creating the shell will reset the seed and you will lose the shell* 39 | 40 | * *Seed Divisions* : Number of subdivisions in the seed mesh. A value of 2 or 3 should work. Some big models may need high number of initial subdivisions. 41 | *Warning: Very high number of divisions may crash blender. While dragging the value it will be limited to 4, but you can enter a greater number via keyboard* 42 | 43 | * *Target Object* : Pick the model you wish to hollow out from the drop down list. 44 | 45 | * *Minimum Thickness* : This is the thickness of the wall. Any part of the model thinner than this value remains untouched. 46 | 47 | * *Step* : This is the distance the shell grows in each iteration. 48 | *Note: Smaller values produce more accurate results, but will slow it down. This value should be less than the minimum thickness, lets say about a tenth of it.* 49 | 50 | * *Redraw Delay* : Controls how often the screen is refreshed. 51 | 52 | * *Max Triangle Area* : The seed mesh expands, and so do the triangles. When they reach this area, they are subdivided. A smaller value will produce a dense mesh. A larger value will produce a low poly shell. 53 | 54 | * *Iterations* : The number of times the expansion of the seed happens. 55 | *Note : A low value may leave some volume unfilled. In that case simply click the *Create Shell* button again, it expands the existing shell. A high value may cause the shell to fold within itself, which is not good.* 56 | 57 | * *Drill Count* : Number of drills (Cylinders) you need. Usually specified by the 3D printer. Bigger models need more holes. 58 | 59 | * *Hole size* : The diameter of the holes. Usually specified by the 3D printer. Bigger models need bigger holes. 60 | 61 | * *Drill length* : Keep it more than twice the wall thickness, so that you can see them and they penetrate the wall completely. 62 | 63 | * *Drill Sides* : Sides or vertices of the cap of the cylinders. 64 | 65 | * *Delete Drills* : Deletes the drills after making holes. 66 | 67 | * *Help|Source|Updates* : Brings you here. 68 | 69 | 70 | **Actions** 71 | 72 | * *Create Seed* : Creates a sphere, which is the seed that expands to fill the model volume. 73 | 74 | * *Create Shell* : Creates the shell. You can keep clicking this button till the seed fills the target model. You can also change the thickness or triangle area and click this button again to refine the shell. 75 | 76 | * *Flip & Attach* : Flips the normals of shell and joins it with your model. This effectively makes it hollow. 77 | 78 | * *Create Drills* : Creates cylinders with specified dimensions. 79 | 80 | * *Drill Holes* : Creates boolean of your model with the drills. In other words, drills the holes. Holes are needed to drain out the supporting material from inside of the printed model. The number of holes and their dia are specified by the printer and depends on the material. 81 | 82 | **Info Panel** 83 | 84 | The info panel displays some useful info such as dimensions, volume and units in use. 85 | 86 | The addon depends on correct unit settings. You can set the units from the *Scene Properties* tab in Properties window. If you are using Metric units and want to display dimensions in mm, you should set the unit scale to 0.001. For cm it should be 0.01 etc. 87 | 88 | **Sample Model** 89 | 90 | A sample blend file is provided with the addon to test it out. This tool is highly dependent on the geometry and size of the models. You will need to adjust the parameters for each model. Units may also affect the working of this tool. The sample file has everything set right for use with default parameters. 91 | 92 | **Known Issues** 93 | 94 | * Blender may report a wrong volume when very low poly objects (such as a cube with 6 polys) are hollowed out. 95 | * If some part of the seed remains outside the object, it keeps growing outside till the number of iterations are over. It should stop as soon as the volume of the seed is greater than the volume of the object. 96 | * If there is no active object, it can throw an error or can disable a button. This can happen for example when you delete something and no object is active. Just select your model and continue. 97 | 98 | **Other uses** 99 | 100 | It can be used as a remeshing tool if you set the wall thickness very low (e.g. 0.1). A low poly replica of the high poly model can be obtained. 101 | 102 | **Misc Info** 103 | 104 | This plugin has been released under MIT license, which means it is free for any kind of use and modification, but has no warranties or liabilities. Please read the license before you download and use it. 105 | 106 | **About** 107 | 108 | 109 | --- 110 | 111 | A FOSS Project by Oormi Creations 112 | 113 | http://oormi.in 114 | 115 | oormicreations@gmail.com 116 | 117 | 118 | ![logo](https://oormi.in/software/cbp/images/OormiLogo.png) 119 | 120 | December 2019. 121 | -------------------------------------------------------------------------------- /blendshell-dog-tex.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oormicreations/BlendShell/6e3736553ff743c92c9c1a83c87c28480070819d/blendshell-dog-tex.jpg -------------------------------------------------------------------------------- /blendshell.py: -------------------------------------------------------------------------------- 1 | # Mesh hollow automation by oormi creations 2 | # Creates a hollow mesh as per specifictions of minimum thickness. 3 | # Reduces the cost of 3D Print. 4 | # Most useful for high poly organic models. Sculpted models. 5 | 6 | 7 | bl_info = { 8 | "name": "BlendShell", 9 | "description": "Mesh hollow automation by oormi creations", 10 | "author": "Oormi Creations", 11 | "version": (0, 4, 0), 12 | "blender": (2, 80, 0), 13 | "location": "3D View > BlendShell", 14 | "warning": "", # used for warning icon and text in addons panel 15 | "wiki_url": "", 16 | "tracker_url": "", 17 | "category": "Development" 18 | } 19 | 20 | import bpy 21 | import bmesh 22 | from bpy.props import (StringProperty, 23 | BoolProperty, 24 | IntProperty, 25 | FloatProperty, 26 | FloatVectorProperty, 27 | EnumProperty, 28 | PointerProperty, 29 | ) 30 | from bpy.types import (Panel, 31 | Menu, 32 | Operator, 33 | PropertyGroup, 34 | ) 35 | 36 | import os 37 | from bpy import context 38 | import time 39 | 40 | 41 | # ------------------------------------------------------------------------ 42 | # Operators 43 | # ------------------------------------------------------------------------ 44 | 45 | def ShowMessageBox(message = "", title = "BlendShell Says...", icon = 'INFO'): 46 | 47 | def draw(self, context): 48 | self.layout.label(text=message) 49 | 50 | bpy.context.window_manager.popup_menu(draw, title = title, icon = icon) 51 | 52 | 53 | def printconsole(data): 54 | for window in bpy.context.window_manager.windows: 55 | screen = window.screen 56 | for area in screen.areas: 57 | if area.type == 'CONSOLE': 58 | override = {'window': window, 'screen': screen, 'area': area} 59 | bpy.ops.console.scrollback_append(override, text=str(data), type="OUTPUT") 60 | 61 | def Create_Seed(div, dia): 62 | 63 | seed = bpy.data.objects.get("Seed.001") 64 | if seed is not None: 65 | bpy.ops.object.select_all(action='DESELECT') 66 | seed.select_set(True) 67 | bpy.context.scene.cursor.location = seed.location 68 | bpy.ops.object.delete() 69 | 70 | bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=div, radius=dia/2.0, location=bpy.context.scene.cursor.location) 71 | seed = bpy.context.selected_objects[0] 72 | seed.name = "Seed.001" 73 | 74 | #printconsole("Seed created") 75 | 76 | def On_Seedsz_Changed(self, context): 77 | bstool = context.scene.bs_tool 78 | Create_Seed(bstool.bs_seeddiv, bstool.bs_seedsz) 79 | 80 | 81 | class CSH_OT_CCreateShell(bpy.types.Operator): 82 | bl_idname = "bscreate.shell" 83 | bl_label = "Create Shell" 84 | bl_description = "Create the hollow shell" 85 | 86 | def execute(self, context): 87 | ######### Remove ###### 88 | #bpy.ops.object.delete() # 89 | ######################### 90 | scene = context.scene 91 | bstool = scene.bs_tool 92 | 93 | seedsz = bstool.bs_seedsz 94 | seeddiv = bstool.bs_seeddiv 95 | thickness = bstool.bs_thickness 96 | dv = bstool.bs_dv 97 | rdelay = bstool.bs_rdelay 98 | pszmax = bstool.bs_pszmax 99 | itrs = bstool.bs_itrs 100 | 101 | seed = bpy.data.objects.get("Seed.001") 102 | if seed == None: 103 | ShowMessageBox("Please create a seed, position it inside your model and try again") 104 | return{'FINISHED'} 105 | 106 | target = bstool.bs_target 107 | #target = bpy.context.object 108 | 109 | if target == None or target == seed: 110 | ShowMessageBox("Please pick your model from the Target Object dropdown box and try again") 111 | return{'FINISHED'} 112 | 113 | printconsole(target) 114 | mp = target.data 115 | bme = bmesh.new() 116 | bme.from_mesh(mp) 117 | vol = bme.calc_volume() 118 | printconsole(vol) 119 | smp = seed.data 120 | sbme = bmesh.new() 121 | sbme.from_mesh(smp) 122 | svol = sbme.calc_volume() 123 | printconsole(svol) 124 | 125 | #return{'FINISHED'} 126 | 127 | #checks 128 | if(dv>=thickness): 129 | ShowMessageBox("Step size should be less than minimum thickness.") 130 | return{'FINISHED'} 131 | 132 | #seed.show_wire = True 133 | #seed.show_all_edges = True 134 | #save old origin 135 | oldloc0 = target.location[0] 136 | oldloc1 = target.location[1] 137 | oldloc2 = target.location[2] 138 | #printconsole(oldloc2) 139 | #return{'FINISHED'} 140 | 141 | #fix origin 142 | bpy.ops.object.select_all(action='DESELECT') 143 | bpy.context.view_layer.objects.active = target 144 | target.select_set(True) 145 | bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) 146 | bpy.context.scene.cursor.location = seed.location 147 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR') 148 | bpy.ops.object.select_all(action='DESELECT') 149 | bpy.context.view_layer.objects.active = seed 150 | seed.select_set(True) 151 | 152 | #return{'FINISHED'} 153 | 154 | start_time = time.time() 155 | redrw = 0 156 | unmovable = [] 157 | 158 | for it in range(0,itrs): 159 | #redraw view 160 | redrw = redrw + 1 161 | if redrw > rdelay: 162 | bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) 163 | redrw = 0 164 | printconsole (it) 165 | 166 | #check volume 167 | smp = seed.data 168 | sbme = bmesh.new() 169 | sbme.from_mesh(smp) 170 | svol = sbme.calc_volume() 171 | if(svol>vol): 172 | printconsole("Shell creation failed!") 173 | return{'FINISHED'} 174 | printconsole(svol) 175 | 176 | 177 | #expand seed 178 | for v in seed.data.vertices: 179 | if (v.index in unmovable) == False: 180 | (hit, loc, norm, face_index) = target.closest_point_on_mesh(v.co) 181 | if hit: 182 | dist = (loc - v.co).length 183 | if dist > thickness: 184 | v.co += dv * v.normal 185 | else: 186 | unmovable.append(v.index) 187 | 188 | 189 | #''' 190 | #divide large polys 191 | bpy.ops.object.mode_set(mode='EDIT') 192 | 193 | bm = bmesh.from_edit_mesh(seed.data) 194 | bm.verts.ensure_lookup_table() 195 | bm.edges.ensure_lookup_table() 196 | bm.faces.ensure_lookup_table() 197 | 198 | flist = [] 199 | for p in bm.faces: 200 | p.select = True 201 | if p.calc_area() > pszmax: 202 | #p.select = True 203 | flist.append(p) 204 | 205 | bmesh.ops.poke(bm, faces = flist) 206 | bmesh.update_edit_mesh(seed.data) 207 | bpy.ops.mesh.beautify_fill() 208 | bpy.ops.object.mode_set(mode='OBJECT') 209 | 210 | #''' 211 | 212 | bpy.ops.object.modifier_add(type='SMOOTH') 213 | bpy.context.object.modifiers["Smooth"].iterations = 4 214 | 215 | if bpy.app.version[1] > 89: 216 | bpy.ops.object.modifier_apply(modifier="Smooth") 217 | else: 218 | bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Smooth") 219 | 220 | printconsole ("Shell created !") 221 | printconsole((time.time() - start_time)) 222 | printconsole("--- seconds ---") 223 | bpy.context.scene.cursor.location[0] = oldloc0 224 | bpy.context.scene.cursor.location[1] = oldloc1 225 | bpy.context.scene.cursor.location[2] = oldloc2 226 | 227 | return{'FINISHED'} 228 | 229 | class CFA_OT_CFlipAttach(bpy.types.Operator): 230 | bl_idname = "bsflip.attach" 231 | bl_label = "Flip Attach" 232 | bl_description = "Flips the normals and joins to model" 233 | 234 | def execute(self, context): 235 | 236 | scene = context.scene 237 | bstool = scene.bs_tool 238 | 239 | seed = bpy.data.objects.get("Seed.001") 240 | if seed == None: 241 | ShowMessageBox("Seed not found!") 242 | return{'FINISHED'} 243 | 244 | target = bstool.bs_target 245 | #target = bpy.context.object 246 | if target == None or target == bpy.data.objects['Seed.001']: 247 | ShowMessageBox("Please pick your model from the Target Object dropdown box and try again") 248 | return{'FINISHED'} 249 | 250 | bpy.ops.object.select_all(action='DESELECT') 251 | bpy.context.view_layer.objects.active = seed 252 | seed.select_set(True) 253 | 254 | bpy.ops.object.mode_set(mode='EDIT') 255 | bpy.ops.mesh.flip_normals() 256 | bpy.ops.object.mode_set(mode='OBJECT') 257 | target.select_set(True) 258 | bpy.context.view_layer.objects.active = target 259 | bpy.ops.object.join() 260 | 261 | #reset location 262 | # bpy.ops.object.origin_set(type='ORIGIN_CURSOR', center='MEDIAN') 263 | # bpy.context.selected_objects[0].location = [0,0,0] 264 | # bpy.context.scene.cursor.location[1] = 0 265 | # bpy.context.scene.cursor.location[2] = 0 266 | # bpy.context.scene.cursor.location[0] = 0 267 | 268 | printconsole ("Flipped & Attached!") 269 | return{'FINISHED'} 270 | 271 | 272 | class CDH_OT_CDrillHoles(bpy.types.Operator): 273 | bl_idname = "bsdrill.holes" 274 | bl_label = "Drill Holes" 275 | bl_description = "Make holes in hollow model" 276 | 277 | @classmethod 278 | def poll(self,context): 279 | return context.object is not None 280 | 281 | def execute(self, context): 282 | 283 | scene = context.scene 284 | bstool = scene.bs_tool 285 | 286 | #target = bpy.context.selected_objects[0] 287 | target = bstool.bs_target 288 | if target == None: 289 | ShowMessageBox("Please pick your model from the Target Object dropdown box and try again") 290 | return{'FINISHED'} 291 | 292 | bpy.ops.object.mode_set(mode='OBJECT') 293 | bpy.ops.object.select_all(action='DESELECT') 294 | bpy.context.view_layer.objects.active = target 295 | target.select_set(True) 296 | 297 | drills = [obj for obj in bpy.data.objects if obj.name.startswith("Drill")] 298 | 299 | if len(drills) < 1: 300 | ShowMessageBox("Please create some drills, position them and try again") 301 | return{'FINISHED'} 302 | 303 | for d in drills: 304 | printconsole(d.name) 305 | bpy.ops.object.modifier_add(type='BOOLEAN') 306 | bpy.context.object.modifiers["Boolean"].object = d 307 | bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Boolean") 308 | if bstool.bs_deldrills: 309 | bpy.ops.object.select_all(action='DESELECT') 310 | d.select_set(True) 311 | bpy.ops.object.delete() 312 | 313 | printconsole ("Holes Drilled!") 314 | return{'FINISHED'} 315 | 316 | 317 | class CCD_OT_CCreateDrills(bpy.types.Operator): 318 | bl_idname = "bscreate.drills" 319 | bl_label = "Create Drills" 320 | bl_description = "Create cylindrical drills for making holes" 321 | 322 | def execute(self, context): 323 | 324 | scene = context.scene 325 | bstool = scene.bs_tool 326 | rad=bstool.bs_drilldia/2 327 | 328 | for i in range(0, bstool.bs_ndrills): 329 | bpy.ops.mesh.primitive_cylinder_add(vertices=bstool.bs_drillsides, radius=rad, \ 330 | depth=bstool.bs_drillsz, location=(i*bstool.bs_drilldia, 0, 0)) 331 | drill = bpy.context.selected_objects[0] 332 | drill.name = "Drill.001" 333 | 334 | printconsole ("Drills Created!") 335 | return{'FINISHED'} 336 | 337 | class CCS_OT_CCreateSeed(bpy.types.Operator): 338 | bl_idname = "bscreate.seed" 339 | bl_label = "Create Seed" 340 | bl_description = "Create seed mesh to fill the model" 341 | 342 | def execute(self, context): 343 | bstool = context.scene.bs_tool 344 | Create_Seed(bstool.bs_seeddiv, bstool.bs_seedsz) 345 | printconsole ("Seed Created") 346 | return{'FINISHED'} 347 | 348 | ############################ Panels ############################################## 349 | 350 | class OBJECT_PT_BlendShellPanel(Panel): 351 | bl_label = "BlendShell 0.3.0" 352 | bl_idname = "OBJECT_PT_Bs_Panel" 353 | bl_category = "BlendShell" 354 | bl_space_type = 'VIEW_3D' 355 | bl_region_type = 'UI' 356 | bl_context = "objectmode" 357 | 358 | # @classmethod 359 | # def poll(self,context): 360 | # return context.object is not None 361 | 362 | def draw(self, context): 363 | layout = self.layout 364 | scene = context.scene 365 | bstool = scene.bs_tool 366 | 367 | layout.operator("bscreate.seed", text = "Create Seed", icon='MESH_UVSPHERE') 368 | layout.prop(bstool, "bs_seedsz") 369 | layout.prop(bstool, "bs_seeddiv") 370 | layout.prop_search(bstool, "bs_target", scene, "objects") 371 | layout.prop(bstool, "bs_thickness") 372 | layout.prop(bstool, "bs_dv") 373 | layout.prop(bstool, "bs_rdelay") 374 | layout.prop(bstool, "bs_pszmax") 375 | layout.prop(bstool, "bs_itrs") 376 | layout.operator("bscreate.shell", text = "Create Shell", icon='TRIA_RIGHT') 377 | row = layout.row(align=True) 378 | 379 | class OBJECT_PT_BlendShellPostPanel(bpy.types.Panel): 380 | 381 | bl_label = "Prepare for Print" 382 | bl_idname = "OBJECT_PT_BlendShell_PostPanel" 383 | bl_category = "BlendShell" 384 | bl_space_type = 'VIEW_3D' 385 | bl_region_type = 'UI' 386 | bl_context = "objectmode" 387 | 388 | def draw(self, context): 389 | layout = self.layout 390 | scene = context.scene 391 | bstool = scene.bs_tool 392 | 393 | layout.operator("bsflip.attach", text = "Flip & Attach", icon='TRIA_RIGHT') 394 | layout.prop(bstool, "bs_ndrills") 395 | layout.prop(bstool, "bs_drilldia") 396 | layout.prop(bstool, "bs_drillsz") 397 | layout.prop(bstool, "bs_drillsides") 398 | layout.operator("bscreate.drills", text = "Create Drills", icon='MESH_CYLINDER') 399 | layout.operator("bsdrill.holes", text = "Drill Holes", icon='TRIA_RIGHT') 400 | layout.prop(bstool, "bs_deldrills") 401 | 402 | 403 | class OBJECT_PT_InfoPanel(Panel): 404 | bl_label = "Info" 405 | bl_idname = "OBJECT_PT_Info_Panel" 406 | bl_category = "BlendShell" 407 | bl_space_type = 'VIEW_3D' 408 | bl_region_type = 'UI' 409 | bl_context = "objectmode" 410 | 411 | 412 | def draw(self, context): 413 | layout = self.layout 414 | scene = context.scene 415 | bstool = scene.bs_tool 416 | 417 | layout.label(text = "Target Dimensions :") 418 | if bstool.bs_target is not None: 419 | mp = bstool.bs_target.data 420 | bme = bmesh.new() 421 | bme.from_mesh(mp) 422 | vol = bme.calc_volume() 423 | d = bstool.bs_target.dimensions 424 | layout.label(text = str(d[0])[:8] + " x " + str(d[1])[:8] + " x " + str(d[2])[:8]) 425 | layout.label(text = "Volume = " + str(vol)[:12]) 426 | else: 427 | layout.label(text = "Object not specified") 428 | 429 | layout.label(text = "Current Units : " + scene.unit_settings.system.capitalize()\ 430 | + " - " + scene.unit_settings.length_unit.capitalize()) 431 | layout.label(text = "Unit Scale = " + str(scene.unit_settings.scale_length)[:8]) 432 | layout.label(text = "Unit settings are available") 433 | layout.label(text = "in Scene tab in Properties window") 434 | 435 | row = layout.row(align=True) 436 | row.operator("wm.url_open", text="Help | Source | Updates", icon='QUESTION').url = "https://github.com/oormicreations/BlendShell" 437 | 438 | class CCProperties(PropertyGroup): 439 | 440 | bs_target: PointerProperty( 441 | name = "Target Object", 442 | description = "Select the model that needs to be hollowed", 443 | type = bpy.types.Object 444 | ) 445 | 446 | bs_thickness: FloatProperty( 447 | name = "Min Thickness", 448 | description = "Minimum thickness of the shell", 449 | default = 3.5, 450 | unit= 'LENGTH', 451 | precision = 6, 452 | min=0.001, 453 | max=100000.0 454 | ) 455 | bs_seedsz: FloatProperty( 456 | name = "Seed Size", 457 | description = "Initial size of the seed shell", 458 | default = 10.0, 459 | precision = 6, 460 | min=0.001, 461 | max=100000.0, 462 | unit= 'LENGTH', 463 | update=On_Seedsz_Changed 464 | ) 465 | bs_seeddiv: IntProperty( 466 | name = "Seed Divisions", 467 | description = "Number of initial divisions of seed.\nMax value while dragging is 4.\nEnter to set more than 4 divisions", 468 | default = 2, 469 | min=1, 470 | max=16, 471 | soft_max=4, 472 | update=On_Seedsz_Changed 473 | ) 474 | bs_dv: FloatProperty( 475 | name = "Expansion Step", 476 | description = "Step size for each iteration of seed expansion", 477 | default = 0.35, 478 | precision = 6, 479 | step=1, 480 | unit= 'LENGTH', 481 | min=0.001, 482 | max=1000.0 483 | ) 484 | bs_rdelay: IntProperty( 485 | name = "Redraw Delay", 486 | description = "Redraw views faster or slower", 487 | default = 10, 488 | min=1, 489 | max=100 490 | ) 491 | bs_pszmax: FloatProperty( 492 | name = "Max Triangle Area", 493 | description = "Maximum triangle area after which it gets subdivided", 494 | default = 4.0, 495 | precision = 6, 496 | unit= 'AREA', 497 | min=0.01, 498 | max=10000.0 499 | ) 500 | bs_itrs: IntProperty( 501 | name = "Iterations", 502 | description = "Maximum number of iterations", 503 | default = 200, 504 | min=1, 505 | max=100000 506 | ) 507 | bs_ndrills: IntProperty( 508 | name = "Drill Count", 509 | description = "Number of drills, Number of holes", 510 | default = 2, 511 | min=1, 512 | max=20 513 | ) 514 | bs_drilldia: FloatProperty( 515 | name = "Hole size", 516 | description = "Dia of the holes, size of drills", 517 | default = 5.0, 518 | unit= 'LENGTH', 519 | precision = 6, 520 | min=0.1, 521 | max=1000.0 522 | ) 523 | bs_drillsz: FloatProperty( 524 | name = "Drill Length", 525 | description = "Length of the drills", 526 | default = 15.0, 527 | unit= 'LENGTH', 528 | precision = 6, 529 | min=0.1, 530 | max=10000.0 531 | ) 532 | bs_drillsides: IntProperty( 533 | name = "Drill Sides", 534 | description = "Number of sides or vertices of cap", 535 | default = 32, 536 | min=3, 537 | max=128 538 | ) 539 | bs_deldrills: BoolProperty( 540 | name = "Delete Drills", 541 | description = "Delete the drills after making holes", 542 | default = True 543 | ) 544 | # bs_units: FloatProperty( 545 | # name = "Unit Scale", 546 | # description = "Current units scale.\nCheck the Scene Properties tab to set the units", 547 | # default = 1.0 548 | # ) 549 | 550 | 551 | # ------------------------------------------------------------------------ 552 | # Registration 553 | # ------------------------------------------------------------------------ 554 | 555 | classes = ( 556 | CCS_OT_CCreateSeed, 557 | CSH_OT_CCreateShell, 558 | CFA_OT_CFlipAttach, 559 | CCD_OT_CCreateDrills, 560 | CDH_OT_CDrillHoles, 561 | CCProperties, 562 | OBJECT_PT_BlendShellPanel, 563 | OBJECT_PT_BlendShellPostPanel, 564 | OBJECT_PT_InfoPanel 565 | ) 566 | 567 | def register(): 568 | bl_info['blender'] = getattr(bpy.app, "version") 569 | from bpy.utils import register_class 570 | for cls in classes: 571 | register_class(cls) 572 | 573 | bpy.types.Scene.bs_tool = PointerProperty(type=CCProperties) 574 | 575 | def unregister(): 576 | from bpy.utils import unregister_class 577 | for cls in reversed(classes): 578 | unregister_class(cls) 579 | del bpy.types.Scene.bs_tool 580 | 581 | 582 | if __name__ == "__main__": 583 | register() 584 | -------------------------------------------------------------------------------- /blendshelltest-01.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oormicreations/BlendShell/6e3736553ff743c92c9c1a83c87c28480070819d/blendshelltest-01.blend --------------------------------------------------------------------------------