Skip to content

Instantly share code, notes, and snippets.

@ocommaj
Last active May 3, 2021 03:33
Show Gist options
  • Save ocommaj/d9b2e0b68f300f9afe9f559d660b2b23 to your computer and use it in GitHub Desktop.
Save ocommaj/d9b2e0b68f300f9afe9f559d660b2b23 to your computer and use it in GitHub Desktop.
DemoRack
bl_info = {
"name": "DemoRack",
"description": "Make Mini Rack Units Dynamically",
"author": "Jim O'Connor <hello@ocommaj.com>",
"version": (0, 0, 1),
"blender": (2, 90, 1),
"category": "3D View"
}
module_names = [ 'standoff_props', 'standoff_operator', 'standoff_panel' ]
import sys
import importlib
module_full_names = [ f"{__name__}.{module}" for module in module_names ]
for module in module_full_names:
if module in sys.modules:
importlib.reload(sys.modules[module])
else:
locals()[module] = importlib.import_module(module)
setattr(locals()[module], 'module_names', module_full_names)
def register():
for module in module_full_names:
if module in sys.modules:
if hasattr(sys.modules[module], 'register'):
sys.modules[module].register()
def unregister():
for module in module_full_names:
if module in sys.modules:
if hasattr(sys.modules[module], 'unregister'):
sys.modules[module].unregister()
import bpy
import bmesh
class Standoff:
def __init__(self, name="Std", m_diam=3, depth=3, segments=64):
self.name = name
self.depth = depth
self.segments = segments
self.radii = self.__radii(m_diam)
def mesh(self, depth=None, m_diam=None):
"""
accepts optional args depth and m_diam, updates on self
returns instance of bpy.types.Mesh with standoff geom
"""
if depth:
self.depth = depth
if m_diam:
self.radii = self.__radii(m_diam)
bm = self.__create_drum_bmesh()
return bmesh_to_mesh(bm)
def __create_drum_bmesh(self, bm=None):
"""
returns new bmesh instance for current self geometry values
'bm' variable name is BMesh convention following docs:
https://docs.blender.org/api/current/bmesh.html
"""
if not bm:
bm = bmesh.new()
to_extrude = self.__make_footprint(bm)
extrude_faces(bm, to_extrude["faces"], self.depth)
return bm
def __make_footprint(self, bm):
"""
takes bmesh instance,
returns dict with keys 'faces', 'edges' from bmesh.ops.bridge_loops
"""
def circumference(radius):
"""
for radius, create circle in bm, return edges list
"""
edges = []
circ = bmesh.ops.create_circle(
bm,
radius=radius,
segments=self.segments,
cap_ends=False
)
[ edges.append(e) for v in circ["verts"]
for e in v.link_edges
if e not in edges ]
return edges
edges = [ e for r in self.radii.values() for e in circumference(r) ]
bridged = bmesh.ops.bridge_loops(bm, edges=edges)
return bridged
def __radii(self, diameter):
return { "inner": diameter/2, "outer": diameter*1.25 }
def extrude_faces(bm, faces, depth=1.0):
extruded = bmesh.ops.extrude_face_region(bm, geom=faces)
verts=[e for e in extruded["geom"] if isinstance(e, bmesh.types.BMVert)]
del extruded
bmesh.ops.translate(bm, verts=verts, vec=(0.0, 0.0, depth))
def clean_for_manifold(bm):
"""helper method to remove doubles; not needed with clean/simple extrude"""
MERGE_DISTANCE = 0.0001
verts = bm.verts
bmesh.ops.remove_doubles(bm, verts=verts, dist=MERGE_DISTANCE)
def bmesh_to_mesh(bm, name=None, me=None):
"""
optional 'me' arg accepts instance of bpy.types.Mesh or creates new
'me' variable name is BMesh convention following docs example:
https://docs.blender.org/api/current/bmesh.html
"""
if not name: name = "Mesh"
if not me: me = bpy.data.meshes.new(name)
bm.to_mesh(me)
bm.free()
return me
def test(m_diam=2.5, depth=3, name="Standoff"):
def add_mesh_to_collection(me, name):
"""
gets reference to collection in active bpy.context,
creates new object with 'me' Mesh arg as obj data value
links created object into referenced collection
"""
collection = bpy.context.collection.objects
obj = bpy.data.objects.new(name, me)
collection.link(obj)
std = Standoff(m_diam=m_diam, depth=depth, name=name)
add_mesh_to_collection(std.mesh(), std.name)
if __name__ == "__main__":
test()
import bpy
from bpy.types import Operator
from bpy.utils import register_class, unregister_class
class DEMORACK_OT_AddNewStandoff(Operator):
"""adds standoff to test add-on registered ok"""
bl_idname = 'scene.add_new_standoff'
bl_label = 'New Standoff'
bl_options = { "REGISTER", "UNDO" }
def execute(self, context):
name = "Standoff"
standoff = context.scene.Standoff # <- set in standoff_props.register()
collection = context.scene.collection
obj = bpy.data.objects.new(name, standoff.mesh)
collection.objects.link(obj)
obj.select_set(True)
context.view_layer.objects.active = obj
return { "FINISHED" }
def register():
register_class(DEMORACK_OT_AddNewStandoff)
def unregister():
unregister_class(DEMORACK_OT_AddNewStandoff)
from bpy.types import Panel
from bpy.utils import register_class, unregister_class
class DemoRackPanel:
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "DemoRack"
class StandoffPanel(DemoRackPanel, Panel):
bl_idname = "DEMORACK_PT_standoff_panel"
bl_label = "Standoff"
def draw(self, context):
layout = self.layout
standoff_data = context.scene.Standoff # <- set in standoff_props.register()
layout.operator("scene.add_new_standoff") # <- registered in standoff_operator.py
layout.prop(standoff_data, "metric_diameter")
layout.prop(standoff_data, "height")
def register():
register_class(StandoffPanel)
def unregister():
unregister_class(StandoffPanel)
from bpy.props import PointerProperty, FloatProperty
from bpy.types import Mesh, PropertyGroup, Scene
from bpy.utils import register_class, unregister_class
from .standoff_mesh import Standoff
def prop_methods(call, prop=None):
def getter(self):
try:
value = self[prop]
except:
set_default = prop_methods("SET", prop)
set_default(self, self.defaults[prop])
if hasattr(self, "on_load"):
self.on_load()
value = self[prop]
finally:
return value
def setter(self, value):
self[prop] = value
def updater(self, context):
self.update(context)
methods = {
"GET": getter,
"SET": setter,
"UPDATE": updater,
}
return methods[call]
class PG_Standoff(PropertyGroup):
metric_diameter: FloatProperty(
name="Inner Diameter (Metric)",
min=2,
max=5,
step=50,
precision=1,
set=prop_methods("SET", "metric_diameter"),
get=prop_methods("GET", "metric_diameter"),
update=prop_methods("UPDATE"))
height: FloatProperty(
name="Standoff Height",
min=2,
max=6,
step=25,
precision=2,
set=prop_methods("SET", "height"),
get=prop_methods("GET", "height"),
update=prop_methods("UPDATE"))
mesh: PointerProperty(type=Mesh)
defaults = { "metric_diameter": 2.5, "height": 3 }
standoff = Standoff()
def on_load(self):
if self.height and self.metric_diameter:
self.__set_mesh()
def update(self, context):
self.__set_mesh()
def __set_mesh(self):
self.mesh = self.standoff.mesh(self.height, self.metric_diameter)
def register():
register_class(PG_Standoff)
Scene.Standoff = PointerProperty(type=PG_Standoff)
def unregister():
unregister_class(PG_Standoff)
del Scene.Standoff
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment