Last active
May 3, 2021 03:33
-
-
Save ocommaj/d9b2e0b68f300f9afe9f559d660b2b23 to your computer and use it in GitHub Desktop.
DemoRack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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