Remove to contrib: add advanced objects series: T63750
This commit is contained in:
parent
654d27c6ca
commit
713fa9b2c4
Notes:
blender-bot
2023-02-14 18:07:15 +01:00
Referenced by commit f3d58200: lighting_tri_lights: Initial commit to release (standalone) T65823713fa9b2c4
Referenced by commitf3d58200
, lighting_tri_lights: Initial commit to release (standalone) T65823713fa9b2c4
|
@ -1,547 +0,0 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# Contributed to by:
|
||||
# meta-androcto, Bill Currie, Jorge Hernandez - Melenedez Jacob Morris, Oscurart #
|
||||
# Rebellion, Antonis Karvelas, Eleanor Howick, lijenstina, Daniel Schalla, Domlysz #
|
||||
# Unnikrishnan(kodemax), Florian Meyer, Omar ahmed, Brian Hinton (Nichod), liero #
|
||||
# Atom, Dannyboy, Mano-Wii, Kursad Karatas, teldredge, Phil Cote #
|
||||
|
||||
bl_info = {
|
||||
"name": "Add Advanced Objects",
|
||||
"author": "Meta Androcto",
|
||||
"version": (0, 1, 6),
|
||||
"blender": (2, 78, 0),
|
||||
"location": "View3D > Add ",
|
||||
"description": "Add Object & Camera extras",
|
||||
"warning": "",
|
||||
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6"
|
||||
"/Py/Scripts/Object/Add_Advanced",
|
||||
"category": "Object"}
|
||||
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
|
||||
importlib.reload(add_light_template)
|
||||
importlib.reload(scene_objects_bi)
|
||||
importlib.reload(scene_objects_cycles)
|
||||
importlib.reload(scene_texture_render)
|
||||
importlib.reload(trilighting)
|
||||
importlib.reload(pixelate_3d)
|
||||
importlib.reload(object_add_chain)
|
||||
importlib.reload(oscurart_chain_maker)
|
||||
importlib.reload(circle_array)
|
||||
importlib.reload(copy2)
|
||||
importlib.reload(make_struts)
|
||||
importlib.reload(random_box_structure)
|
||||
importlib.reload(cubester)
|
||||
importlib.reload(rope_alpha)
|
||||
importlib.reload(add_mesh_aggregate)
|
||||
importlib.reload(arrange_on_curve)
|
||||
importlib.reload(mesh_easylattice)
|
||||
|
||||
else:
|
||||
from . import add_light_template
|
||||
from . import scene_objects_bi
|
||||
from . import scene_objects_cycles
|
||||
from . import scene_texture_render
|
||||
from . import trilighting
|
||||
from . import pixelate_3d
|
||||
from . import object_add_chain
|
||||
from . import oscurart_chain_maker
|
||||
from . import circle_array
|
||||
from . import copy2
|
||||
from . import make_struts
|
||||
from . import random_box_structure
|
||||
from . import cubester
|
||||
from . import rope_alpha
|
||||
from . import add_mesh_aggregate
|
||||
from . import arrange_on_curve
|
||||
from . import mesh_easylattice
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
AddonPreferences,
|
||||
Menu,
|
||||
PropertyGroup,
|
||||
)
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
StringProperty,
|
||||
PointerProperty,
|
||||
)
|
||||
|
||||
|
||||
# Define the "Scenes" menu
|
||||
class VIEW3D_MT_scene_elements_add(Menu):
|
||||
bl_idname = "VIEW3D_MT_scene_elements"
|
||||
bl_label = "Test Scenes"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator_context = 'INVOKE_REGION_WIN'
|
||||
layout.operator("bi.add_scene",
|
||||
text="Scene_Objects_BI")
|
||||
layout.operator("objects_cycles.add_scene",
|
||||
text="Scene_Objects_Cycles")
|
||||
layout.operator("objects_texture.add_scene",
|
||||
text="Scene_Textures_Cycles")
|
||||
|
||||
|
||||
# Define the "Lights" menu
|
||||
class VIEW3D_MT_mesh_lights_add(Menu):
|
||||
bl_idname = "VIEW3D_MT_scene_lights"
|
||||
bl_label = "Lighting Sets"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator_context = 'INVOKE_REGION_WIN'
|
||||
layout.operator("object.add_light_template",
|
||||
text="Add Light Template")
|
||||
layout.operator("object.trilighting",
|
||||
text="Add Tri Lighting")
|
||||
|
||||
|
||||
# Define the "Chains" menu
|
||||
class VIEW3D_MT_mesh_chain_add(Menu):
|
||||
bl_idname = "VIEW3D_MT_mesh_chain"
|
||||
bl_label = "Chains"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator_context = 'INVOKE_REGION_WIN'
|
||||
layout.operator("mesh.primitive_chain_add", icon="LINKED")
|
||||
layout.operator("mesh.primitive_oscurart_chain_add", icon="LINKED")
|
||||
|
||||
|
||||
# Define the "Array" Menu
|
||||
class VIEW3D_MT_array_mods_add(Menu):
|
||||
bl_idname = "VIEW3D_MT_array_mods"
|
||||
bl_label = "Array Mods"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator_context = 'INVOKE_REGION_WIN'
|
||||
|
||||
layout.menu("VIEW3D_MT_mesh_chain", icon="LINKED")
|
||||
|
||||
layout.operator("objects.circle_array_operator",
|
||||
text="Circle Array", icon="MOD_ARRAY")
|
||||
layout.operator("object.agregate_mesh",
|
||||
text="Aggregate Mesh", icon="MOD_ARRAY")
|
||||
layout.operator("mesh.copy2",
|
||||
text="Copy To Vert/Edge", icon="MOD_ARRAY")
|
||||
|
||||
|
||||
# Define the "Blocks" Menu
|
||||
class VIEW3D_MT_quick_blocks_add(Menu):
|
||||
bl_idname = "VIEW3D_MT_quick_tools"
|
||||
bl_label = "Block Tools"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator_context = 'INVOKE_REGION_WIN'
|
||||
|
||||
layout.operator("object.pixelate", icon="MESH_GRID")
|
||||
layout.operator("mesh.generate_struts",
|
||||
text="Struts", icon="GRID")
|
||||
layout.operator("object.make_structure",
|
||||
text="Random Boxes", icon="SEQ_SEQUENCER")
|
||||
layout.operator("object.easy_lattice",
|
||||
text="Easy Lattice", icon="MOD_LATTICE")
|
||||
|
||||
|
||||
# Define the "Phsysics Tools" Menu
|
||||
class VIEW3D_MT_Physics_tools_add(Menu):
|
||||
bl_idname = "VIEW3D_MT_physics_tools"
|
||||
bl_label = "Physics Tools"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator_context = 'INVOKE_REGION_WIN'
|
||||
|
||||
layout.operator("ball.rope",
|
||||
text="Wrecking Ball", icon='PHYSICS')
|
||||
layout.operator("clot.rope",
|
||||
text="Cloth Rope", icon='PHYSICS')
|
||||
|
||||
|
||||
# Define "Extras" menu
|
||||
def menu(self, context):
|
||||
layout = self.layout
|
||||
layout.operator_context = 'INVOKE_REGION_WIN'
|
||||
self.layout.separator()
|
||||
self.layout.menu("VIEW3D_MT_scene_elements", icon="SCENE_DATA")
|
||||
self.layout.menu("VIEW3D_MT_scene_lights", icon="LIGHT_SPOT")
|
||||
self.layout.separator()
|
||||
self.layout.menu("VIEW3D_MT_array_mods", icon="MOD_ARRAY")
|
||||
self.layout.menu("VIEW3D_MT_quick_tools", icon="MOD_BUILD")
|
||||
self.layout.menu("VIEW3D_MT_physics_tools", icon="PHYSICS")
|
||||
|
||||
|
||||
# Addons Preferences
|
||||
class AdvancedObjPreferences(AddonPreferences):
|
||||
bl_idname = __name__
|
||||
|
||||
show_menu_list: BoolProperty(
|
||||
name="Menu List",
|
||||
description="Show/Hide the Add Menu items",
|
||||
default=False
|
||||
)
|
||||
show_panel_list: BoolProperty(
|
||||
name="Panels List",
|
||||
description="Show/Hide the Panel items",
|
||||
default=False
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
icon_1 = "TRIA_RIGHT" if not self.show_menu_list else "TRIA_DOWN"
|
||||
box = layout.box()
|
||||
box.prop(self, "show_menu_list", emboss=False, icon=icon_1)
|
||||
|
||||
if self.show_menu_list:
|
||||
box.label(text="Items located in the Add Menu (default shortcut Ctrl + A):",
|
||||
icon="LAYER_USED")
|
||||
box.label(text="Test Scenes:", icon="LAYER_ACTIVE")
|
||||
box.label(text="Scene Objects BI, Scene Objects Cycles, Scene Textures Cycles",
|
||||
icon="LAYER_USED")
|
||||
box.label(text="Lighting Sets:", icon="LAYER_ACTIVE")
|
||||
box.label(text="Add Light Template, Add Tri Lighting", icon="LAYER_USED")
|
||||
box.label(text="Array Mods:", icon="LAYER_ACTIVE")
|
||||
box.label(text="Circle Array, Chains submenu, Copy Vert/Edge and Aggregate Mesh",
|
||||
icon="LAYER_ACTIVE")
|
||||
box.label(text="Chains Submenu - Add Chain, Chain to Bones",
|
||||
icon="LAYER_ACTIVE")
|
||||
box.label(text="Block Tools:", icon="LAYER_ACTIVE")
|
||||
box.label(text="Pixelate Object, Struts, Random Boxes, Easy Lattice",
|
||||
icon="LAYER_USED")
|
||||
box.label(text="Physics Tools:", icon="LAYER_ACTIVE")
|
||||
box.label(text="Wrecking Ball and Cloth Rope", icon="LAYER_USED")
|
||||
|
||||
icon_2 = "TRIA_RIGHT" if not self.show_panel_list else "TRIA_DOWN"
|
||||
box = layout.box()
|
||||
box.prop(self, "show_panel_list", emboss=False, icon=icon_2)
|
||||
|
||||
if self.show_panel_list:
|
||||
box.label(text="Panels located in 3D View Tools Region > Create",
|
||||
icon="LAYER_ACTIVE")
|
||||
box.label(text="CubeSter", icon="LAYER_USED")
|
||||
box.label(text="Arrange on Curve (Shown if an Active Curve Object is it the 3D View)",
|
||||
icon="LAYER_USED")
|
||||
|
||||
|
||||
# Cubester update functions
|
||||
def find_audio_length(self, context):
|
||||
adv_obj = context.scene.advanced_objects
|
||||
audio_file = adv_obj.cubester_audio_path
|
||||
length = 0
|
||||
|
||||
if audio_file != "":
|
||||
# confirm that strip hasn't been loaded yet
|
||||
get_sequence = getattr(context.scene.sequence_editor, "sequences_all", [])
|
||||
for strip in get_sequence:
|
||||
if type(strip) == bpy.types.SoundSequence and strip.sound.filepath == audio_file:
|
||||
length = strip.frame_final_duration
|
||||
|
||||
if length == 0:
|
||||
area = context.area
|
||||
old_type = area.type
|
||||
area.type = "SEQUENCE_EDITOR"
|
||||
try:
|
||||
bpy.ops.sequencer.sound_strip_add(filepath=audio_file)
|
||||
adv_obj.cubester_check_audio = True
|
||||
except Exception as e:
|
||||
print("\n[Add Advanced Objects]\n Function: "
|
||||
"find_audio_length\n {}\n".format(e))
|
||||
adv_obj.cubester_check_audio = False
|
||||
pass
|
||||
|
||||
area.type = old_type
|
||||
|
||||
# find audio file
|
||||
for strip in context.scene.sequence_editor.sequences_all:
|
||||
if type(strip) == bpy.types.SoundSequence and strip.sound.filepath == audio_file:
|
||||
adv_obj.cubester_check_audio = True
|
||||
length = strip.frame_final_duration
|
||||
|
||||
adv_obj.cubester_audio_file_length = length
|
||||
|
||||
|
||||
# load image if possible
|
||||
def adjust_selected_image(self, context):
|
||||
scene = context.scene.advanced_objects
|
||||
try:
|
||||
image = bpy.data.images.load(scene.cubester_load_image)
|
||||
scene.cubester_image = image.name
|
||||
except Exception as e:
|
||||
print("\n[Add Advanced Objects]\n Function: "
|
||||
"adjust_selected_image\n {}\n".format(e))
|
||||
|
||||
|
||||
# load color image if possible
|
||||
def adjust_selected_color_image(self, context):
|
||||
scene = context.scene.advanced_objects
|
||||
try:
|
||||
image = bpy.data.images.load(scene.cubester_load_color_image)
|
||||
scene.cubester_color_image = image.name
|
||||
except Exception as e:
|
||||
print("\nAdd Advanced Objects]\n Function: "
|
||||
"adjust_selected_color_image\n {}\n".format(e))
|
||||
|
||||
|
||||
class AdvancedObjProperties(PropertyGroup):
|
||||
# cubester
|
||||
# main properties
|
||||
cubester_check_audio: BoolProperty(
|
||||
name="",
|
||||
default=False
|
||||
)
|
||||
cubester_audio_image: EnumProperty(
|
||||
name="Input Type",
|
||||
items=(("image", "Image",
|
||||
"Use an Image as input for generating Geometry", "IMAGE_COL", 0),
|
||||
("audio", "Audio",
|
||||
"Use a Sound Strip as input for generating Geometry", "FILE_SOUND", 1))
|
||||
)
|
||||
cubester_audio_file_length: IntProperty(
|
||||
default=0
|
||||
)
|
||||
# audio
|
||||
cubester_audio_path: StringProperty(
|
||||
default="",
|
||||
name="Audio File",
|
||||
subtype="FILE_PATH",
|
||||
update=find_audio_length
|
||||
)
|
||||
cubester_audio_min_freq: IntProperty(
|
||||
name="Minimum Frequency",
|
||||
min=20, max=100000,
|
||||
default=20
|
||||
)
|
||||
cubester_audio_max_freq: IntProperty(
|
||||
name="Maximum Frequency",
|
||||
min=21, max=999999,
|
||||
default=5000
|
||||
)
|
||||
cubester_audio_offset_type: EnumProperty(
|
||||
name="Offset Type",
|
||||
items=(("freq", "Frequency Offset", ""),
|
||||
("frame", "Frame Offset", "")),
|
||||
description="Type of offset per row of mesh"
|
||||
)
|
||||
cubester_audio_frame_offset: IntProperty(
|
||||
name="Frame Offset",
|
||||
min=0, max=10,
|
||||
default=2
|
||||
)
|
||||
cubester_audio_block_layout: EnumProperty(
|
||||
name="Block Layout",
|
||||
items=(("rectangle", "Rectangular", ""),
|
||||
("radial", "Radial", ""))
|
||||
)
|
||||
cubester_audio_width_blocks: IntProperty(
|
||||
name="Width Block Count",
|
||||
min=1, max=10000,
|
||||
default=5
|
||||
)
|
||||
cubester_audio_length_blocks: IntProperty(
|
||||
name="Length Block Count",
|
||||
min=1, max=10000,
|
||||
default=50
|
||||
)
|
||||
# image
|
||||
cubester_load_type: EnumProperty(
|
||||
name="Image Input Type",
|
||||
items=(("single", "Single Image", ""),
|
||||
("multiple", "Image Sequence", ""))
|
||||
)
|
||||
cubester_image: StringProperty(
|
||||
default="",
|
||||
name=""
|
||||
)
|
||||
cubester_load_image: StringProperty(
|
||||
default="",
|
||||
name="Load Image",
|
||||
subtype="FILE_PATH",
|
||||
update=adjust_selected_image
|
||||
)
|
||||
cubester_skip_images: IntProperty(
|
||||
name="Image Step",
|
||||
min=1, max=30,
|
||||
default=1,
|
||||
description="Step from image to image by this number"
|
||||
)
|
||||
cubester_max_images: IntProperty(
|
||||
name="Max Number Of Images",
|
||||
min=2, max=1000,
|
||||
default=10,
|
||||
description="Maximum number of images to be used"
|
||||
)
|
||||
cubester_frame_step: IntProperty(
|
||||
name="Frame Step Size",
|
||||
min=1, max=10,
|
||||
default=4,
|
||||
description="The number of frames each picture is used"
|
||||
)
|
||||
cubester_skip_pixels: IntProperty(
|
||||
name="Skip # Pixels",
|
||||
min=0, max=256,
|
||||
default=64,
|
||||
description="Skip this number of pixels before placing the next"
|
||||
)
|
||||
cubester_mesh_style: EnumProperty(
|
||||
name="Mesh Type",
|
||||
items=(("blocks", "Blocks", ""),
|
||||
("plane", "Plane", "")),
|
||||
description="Compose mesh of multiple blocks or of a single plane"
|
||||
)
|
||||
cubester_block_style: EnumProperty(
|
||||
name="Block Style",
|
||||
items=(("size", "Vary Size", ""),
|
||||
("position", "Vary Position", "")),
|
||||
description="Vary Z-size of block, or vary Z-position"
|
||||
)
|
||||
cubester_height_scale: FloatProperty(
|
||||
name="Height Scale",
|
||||
subtype="DISTANCE",
|
||||
min=0.1, max=2,
|
||||
default=0.2
|
||||
)
|
||||
cubester_invert: BoolProperty(
|
||||
name="Invert Height",
|
||||
default=False
|
||||
)
|
||||
# general adjustments
|
||||
cubester_size_per_hundred_pixels: FloatProperty(
|
||||
name="Size Per 100 Blocks/Points",
|
||||
subtype="DISTANCE",
|
||||
min=0.001, max=5,
|
||||
default=1
|
||||
)
|
||||
# material based stuff
|
||||
cubester_materials: EnumProperty(
|
||||
name="Material",
|
||||
items=(("vertex", "Vertex Colors", ""),
|
||||
("image", "Image", "")),
|
||||
description="Color with vertex colors, or uv unwrap and use an image"
|
||||
)
|
||||
cubester_use_image_color: BoolProperty(
|
||||
name="Use Original Image Colors'?",
|
||||
default=True,
|
||||
description="Use original image colors, or replace with an another one"
|
||||
)
|
||||
cubester_color_image: StringProperty(
|
||||
default="",
|
||||
name=""
|
||||
)
|
||||
cubester_load_color_image: StringProperty(
|
||||
default="",
|
||||
name="Load Color Image",
|
||||
subtype="FILE_PATH",
|
||||
update=adjust_selected_color_image
|
||||
)
|
||||
cubester_vertex_colors = {}
|
||||
# advanced
|
||||
cubester_advanced: BoolProperty(
|
||||
name="Advanced Options",
|
||||
default=False
|
||||
)
|
||||
cubester_random_weights: BoolProperty(
|
||||
name="Random Weights",
|
||||
default=False
|
||||
)
|
||||
cubester_weight_r: FloatProperty(
|
||||
name="Red",
|
||||
subtype="FACTOR",
|
||||
min=0.01, max=1.0,
|
||||
default=0.25
|
||||
)
|
||||
cubester_weight_g: FloatProperty(
|
||||
name="Green",
|
||||
subtype="FACTOR",
|
||||
min=0.01, max=1.0,
|
||||
default=0.25
|
||||
)
|
||||
cubester_weight_b: FloatProperty(
|
||||
name="Blue",
|
||||
subtype="FACTOR",
|
||||
min=0.01, max=1.0,
|
||||
default=0.25
|
||||
)
|
||||
cubester_weight_a: FloatProperty(
|
||||
name="Alpha",
|
||||
subtype="FACTOR",
|
||||
min=0.01, max=1.0,
|
||||
default=0.25
|
||||
)
|
||||
|
||||
# arrange_on_curve
|
||||
arrange_c_use_selected: BoolProperty(
|
||||
name="Use Selected",
|
||||
description="Use the selected objects to duplicate",
|
||||
default=True,
|
||||
)
|
||||
arrange_c_obj_arranjar: StringProperty(
|
||||
name=""
|
||||
)
|
||||
arrange_c_select_type: EnumProperty(
|
||||
name="Type",
|
||||
description="Select object or group",
|
||||
items=[
|
||||
('O', "Object", "Make duplicates of a specific object"),
|
||||
('G', "Group", "Make duplicates of the objects in a group"),
|
||||
],
|
||||
default='O',
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
bpy.types.Scene.advanced_objects = PointerProperty(
|
||||
type=AdvancedObjProperties
|
||||
)
|
||||
|
||||
# Add "Extras" menu to the "Add" menu
|
||||
bpy.types.VIEW3D_MT_add.append(menu)
|
||||
try:
|
||||
bpy.types.VIEW3D_MT_AddMenu.append(menu)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def unregister():
|
||||
# Remove "Extras" menu from the "Add" menu.
|
||||
bpy.types.VIEW3D_MT_add.remove(menu)
|
||||
try:
|
||||
bpy.types.VIEW3D_MT_AddMenu.remove(menu)
|
||||
except:
|
||||
pass
|
||||
|
||||
bpy.utils.unregister_module(__name__)
|
||||
del bpy.types.Scene.advanced_objects
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,145 +0,0 @@
|
|||
# gpl: author Rebellion
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
from bpy.props import BoolProperty
|
||||
|
||||
|
||||
def add_lights(self, context):
|
||||
|
||||
if self.bKeyLight:
|
||||
keyLight = bpy.data.lights.new(name="Key_Light", type="SPOT")
|
||||
ob = bpy.data.objects.new("Key_Light", keyLight)
|
||||
constraint = ob.constraints.new(type='COPY_LOCATION')
|
||||
constraint.use_offset = True
|
||||
constraint.owner_space = 'LOCAL'
|
||||
constraint.target = self.camera
|
||||
constraint = ob.constraints.new(type='TRACK_TO')
|
||||
constraint.target = self.target
|
||||
constraint.track_axis = 'TRACK_NEGATIVE_Z'
|
||||
constraint.up_axis = 'UP_X'
|
||||
constraint.owner_space = 'LOCAL'
|
||||
bpy.context.collection.objects.link(ob)
|
||||
ob.rotation_euler[2] = -0.785398
|
||||
|
||||
if self.bFillLight:
|
||||
fillLight = bpy.data.lights.new(name="Fill_Light", type="SPOT")
|
||||
ob = bpy.data.objects.new("Fill_Light", fillLight)
|
||||
constraint = ob.constraints.new(type='COPY_LOCATION')
|
||||
constraint.use_offset = True
|
||||
constraint.owner_space = 'LOCAL'
|
||||
constraint.target = self.camera
|
||||
constraint = ob.constraints.new(type='TRACK_TO')
|
||||
constraint.target = self.target
|
||||
constraint.track_axis = 'TRACK_NEGATIVE_Z'
|
||||
constraint.up_axis = 'UP_X'
|
||||
constraint.owner_space = 'LOCAL'
|
||||
bpy.context.collection.objects.link(ob)
|
||||
ob.rotation_euler[2] = 0.785398
|
||||
ob.data.energy = 0.3
|
||||
|
||||
if self.bBackLight:
|
||||
backLight = bpy.data.lights.new(name="Back_Light", type="SPOT")
|
||||
ob = bpy.data.objects.new("Back_Light", backLight)
|
||||
constraint = ob.constraints.new(type='COPY_LOCATION')
|
||||
constraint.use_offset = True
|
||||
constraint.owner_space = 'LOCAL'
|
||||
constraint.target = self.camera
|
||||
constraint = ob.constraints.new(type='TRACK_TO')
|
||||
constraint.target = self.target
|
||||
constraint.track_axis = 'TRACK_NEGATIVE_Z'
|
||||
constraint.up_axis = 'UP_X'
|
||||
constraint.owner_space = 'LOCAL'
|
||||
bpy.context.collection.objects.link(ob)
|
||||
ob.rotation_euler[2] = 3.14159
|
||||
ob.data.energy = 0.2
|
||||
|
||||
if self.camera_constraint and self.camera is not None and \
|
||||
self.camera.type == "CAMERA":
|
||||
|
||||
constraint = self.camera.constraints.new(type='TRACK_TO')
|
||||
constraint.target = self.target
|
||||
constraint.track_axis = 'TRACK_NEGATIVE_Z'
|
||||
constraint.up_axis = 'UP_Y'
|
||||
|
||||
|
||||
class OBJECT_OT_add_light_template(Operator):
|
||||
bl_idname = "object.add_light_template"
|
||||
bl_label = "Add Light Template"
|
||||
bl_description = ("Add Key, Fill and Back Lights to the Scene\n"
|
||||
"Needs an existing Active Object")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
camera = None
|
||||
target = None
|
||||
|
||||
bKeyLight: BoolProperty(
|
||||
name="Key Light",
|
||||
description="Enable Key Light in the Scene",
|
||||
default=True
|
||||
)
|
||||
bFillLight: BoolProperty(
|
||||
name="Fill Light",
|
||||
description="Enable Fill Light in the Scene",
|
||||
default=True
|
||||
)
|
||||
bBackLight: BoolProperty(
|
||||
name="Back Light",
|
||||
description="Enable Back Light in the Scene",
|
||||
default=True
|
||||
)
|
||||
camera_constraint: BoolProperty(
|
||||
name="Camera Constraint",
|
||||
description="Add a Constraint to the Camera Object",
|
||||
default=False
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object is not None
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
objects = context.selected_objects
|
||||
|
||||
if len(objects) == 2:
|
||||
for ob in objects:
|
||||
if ob.type == 'CAMERA':
|
||||
self.camera = ob
|
||||
else:
|
||||
self.target = ob
|
||||
elif len(objects) == 1:
|
||||
if objects[0].type == 'CAMERA':
|
||||
self.camera = objects[0]
|
||||
bpy.ops.object.empty_add()
|
||||
self.target = context.active_object
|
||||
else:
|
||||
self.camera = context.scene.camera
|
||||
self.target = context.active_object
|
||||
elif len(objects) == 0:
|
||||
bpy.ops.object.empty_add()
|
||||
self.target = context.active_object
|
||||
self.camera = context.scene.camera
|
||||
|
||||
add_lights(self, context)
|
||||
|
||||
except Exception as e:
|
||||
self.report({'WARNING'},
|
||||
"Some operations could not be performed (See Console for more info)")
|
||||
|
||||
print("\n[Add Advanced Objects]\nOperator: "
|
||||
"object.add_light_template\nError: {}".format(e))
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_add_light_template)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_add_light_template)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,338 +0,0 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# Simple aggregate of particles / meshes
|
||||
# Copy the selected objects on the active object
|
||||
# Based on the position of the cursor and a defined volume
|
||||
# Allows to control growth by using a Build modifier
|
||||
|
||||
bl_info = {
|
||||
"name": "Aggregate Mesh",
|
||||
"author": "liero",
|
||||
"version": (0, 0, 5),
|
||||
"blender": (2, 70, 0),
|
||||
"location": "View3D > Tool Shelf",
|
||||
"description": "Adds geometry to a mesh like in DLA aggregators",
|
||||
"category": "Object"}
|
||||
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from random import (
|
||||
choice,
|
||||
gauss,
|
||||
seed,
|
||||
)
|
||||
from mathutils import Matrix
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
)
|
||||
from bpy.types import Operator
|
||||
|
||||
|
||||
def use_random_seed(self):
|
||||
seed(self.rSeed)
|
||||
return
|
||||
|
||||
|
||||
def rg(n):
|
||||
return (round(gauss(0, n), 2))
|
||||
|
||||
|
||||
def remover(sel=False):
|
||||
bpy.ops.object.editmode_toggle()
|
||||
if sel:
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.remove_doubles(threshold=0.0001)
|
||||
bpy.ops.object.mode_set()
|
||||
|
||||
|
||||
class OBJECT_OT_agregate_mesh(Operator):
|
||||
bl_idname = "object.agregate_mesh"
|
||||
bl_label = "Aggregate"
|
||||
bl_description = ("Adds geometry to a mesh like in DLA aggregators\n"
|
||||
"Needs at least two selected Mesh objects")
|
||||
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
|
||||
|
||||
volX: FloatProperty(
|
||||
name="Volume X",
|
||||
min=0.1, max=25,
|
||||
default=3,
|
||||
description="The cloud around cursor"
|
||||
)
|
||||
volY: FloatProperty(
|
||||
name="Volume Y",
|
||||
min=0.1, max=25,
|
||||
default=3,
|
||||
description="The cloud around cursor"
|
||||
)
|
||||
volZ: FloatProperty(
|
||||
name="Volume Z",
|
||||
min=0.1, max=25,
|
||||
default=3,
|
||||
description="The cloud around cursor"
|
||||
)
|
||||
baseSca: FloatProperty(
|
||||
name="Scale",
|
||||
min=0.01, max=5,
|
||||
default=.25,
|
||||
description="Particle Scale"
|
||||
)
|
||||
varSca: FloatProperty(
|
||||
name="Var",
|
||||
min=0, max=1,
|
||||
default=0,
|
||||
description="Particle Scale Variation"
|
||||
)
|
||||
rotX: FloatProperty(
|
||||
name="Rot Var X",
|
||||
min=0, max=2,
|
||||
default=0,
|
||||
description="X Rotation Variation"
|
||||
)
|
||||
rotY: FloatProperty(
|
||||
name="Rot Var Y",
|
||||
min=0, max=2,
|
||||
default=0,
|
||||
description="Y Rotation Variation"
|
||||
)
|
||||
rotZ: FloatProperty(
|
||||
name="Rot Var Z",
|
||||
min=0, max=2,
|
||||
default=1,
|
||||
description="Z Rotation Variation"
|
||||
)
|
||||
rSeed: IntProperty(
|
||||
name="Random seed",
|
||||
min=0, max=999999,
|
||||
default=1,
|
||||
description="Seed to feed random values"
|
||||
)
|
||||
numP: IntProperty(
|
||||
name="Number",
|
||||
min=1,
|
||||
max=9999, soft_max=500,
|
||||
default=50,
|
||||
description="Number of particles"
|
||||
)
|
||||
nor: BoolProperty(
|
||||
name="Normal Oriented",
|
||||
default=False,
|
||||
description="Align Z axis with Faces normals"
|
||||
)
|
||||
cent: BoolProperty(
|
||||
name="Use Face Center",
|
||||
default=False,
|
||||
description="Center on Faces"
|
||||
)
|
||||
track: BoolProperty(
|
||||
name="Cursor Follows",
|
||||
default=False,
|
||||
description="Cursor moves as structure grows / more compact results"
|
||||
)
|
||||
anim: BoolProperty(
|
||||
name="Animatable",
|
||||
default=False,
|
||||
description="Sort faces so you can regrow with Build Modifier, materials are lost"
|
||||
)
|
||||
refresh: BoolProperty(
|
||||
name="Update",
|
||||
default=False
|
||||
)
|
||||
auto_refresh: BoolProperty(
|
||||
name="Auto",
|
||||
description="Auto update spline",
|
||||
default=False
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
col = layout.column(align=True)
|
||||
row = col.row(align=True)
|
||||
|
||||
if self.auto_refresh is False:
|
||||
self.refresh = False
|
||||
elif self.auto_refresh is True:
|
||||
self.refresh = True
|
||||
|
||||
row.prop(self, "auto_refresh", toggle=True, icon="AUTO")
|
||||
row.prop(self, "refresh", toggle=True, icon="FILE_REFRESH")
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.separator()
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.prop(self, "volX", slider=True)
|
||||
col.prop(self, "volY", slider=True)
|
||||
col.prop(self, "volZ", slider=True)
|
||||
|
||||
layout.label(text="Particles:")
|
||||
col = layout.column(align=True)
|
||||
col.prop(self, "baseSca", slider=True)
|
||||
col.prop(self, "varSca", slider=True)
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.prop(self, "rotX", slider=True)
|
||||
col.prop(self, "rotY", slider=True)
|
||||
col.prop(self, "rotZ", slider=True)
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.prop(self, "rSeed", slider=False)
|
||||
col.prop(self, "numP")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(self, "nor")
|
||||
row.prop(self, "cent")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(self, "track")
|
||||
row.prop(self, "anim")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (len(bpy.context.selected_objects) > 1 and
|
||||
bpy.context.object.type == 'MESH')
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.refresh = True
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
if not self.refresh:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
scn = bpy.context.scene
|
||||
obj = bpy.context.active_object
|
||||
|
||||
use_random_seed(self)
|
||||
|
||||
mat = Matrix((
|
||||
(1, 0, 0, 0),
|
||||
(0, 1, 0, 0),
|
||||
(0, 0, 1, 0),
|
||||
(0, 0, 0, 1))
|
||||
)
|
||||
if obj.matrix_world != mat:
|
||||
self.report({'WARNING'},
|
||||
"Please, Apply transformations to Active Object first")
|
||||
return{'FINISHED'}
|
||||
|
||||
par = [o for o in bpy.context.selected_objects if o.type == 'MESH' and o != obj]
|
||||
if not par:
|
||||
return{'FINISHED'}
|
||||
|
||||
bpy.ops.object.mode_set()
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
obj.select_set(True)
|
||||
msv = []
|
||||
|
||||
for i in range(len(obj.modifiers)):
|
||||
msv.append(obj.modifiers[i].show_viewport)
|
||||
obj.modifiers[i].show_viewport = False
|
||||
|
||||
cur = scn.cursor.location
|
||||
for i in range(self.numP):
|
||||
|
||||
mes = choice(par).data
|
||||
newobj = bpy.data.objects.new('nuevo', mes)
|
||||
scn.objects.link(newobj)
|
||||
origen = (rg(self.volX) + cur[0], rg(self.volY) + cur[1], rg(self.volZ) + cur[2])
|
||||
|
||||
cpom = obj.closest_point_on_mesh(origen)
|
||||
|
||||
if self.cent:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
if hasattr(bm.verts, "ensure_lookup_table"):
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
newobj.location = bm.faces[cpom[3]].calc_center_median()
|
||||
|
||||
bm.free()
|
||||
else:
|
||||
newobj.location = cpom[1]
|
||||
|
||||
if self.nor:
|
||||
newobj.rotation_mode = 'QUATERNION'
|
||||
newobj.rotation_quaternion = cpom[1].to_track_quat('Z', 'Y')
|
||||
newobj.rotation_mode = 'XYZ'
|
||||
newobj.rotation_euler[0] += rg(self.rotX)
|
||||
newobj.rotation_euler[1] += rg(self.rotY)
|
||||
newobj.rotation_euler[2] += rg(self.rotZ)
|
||||
else:
|
||||
newobj.rotation_euler = (rg(self.rotX), rg(self.rotY), rg(self.rotZ))
|
||||
|
||||
newobj.scale = [self.baseSca + self.baseSca * rg(self.varSca)] * 3
|
||||
|
||||
if self.anim:
|
||||
newobj.select_set(True)
|
||||
bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', obdata=True)
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
|
||||
bme = bmesh.new()
|
||||
bme.from_mesh(obj.data)
|
||||
|
||||
tmp = bmesh.new()
|
||||
tmp.from_mesh(newobj.data)
|
||||
|
||||
for f in tmp.faces:
|
||||
# z = len(bme.verts)
|
||||
for v in f.verts:
|
||||
bme.verts.new(list(v.co))
|
||||
bme.faces.new(bme.verts[-len(f.verts):])
|
||||
|
||||
bme.to_mesh(obj.data)
|
||||
remover(True)
|
||||
# Note: foo.user_clear() is deprecated use do_unlink=True instead
|
||||
bpy.data.meshes.remove(newobj.data, do_unlink=True)
|
||||
|
||||
else:
|
||||
scn.objects.active = obj
|
||||
newobj.select_set(True)
|
||||
bpy.ops.object.join()
|
||||
|
||||
if self.track:
|
||||
cur = scn.cursor.location = cpom[1]
|
||||
|
||||
for i in range(len(msv)):
|
||||
obj.modifiers[i].show_viewport = msv[i]
|
||||
|
||||
for o in par:
|
||||
o.select_set(True)
|
||||
|
||||
obj.select_set(True)
|
||||
|
||||
if self.auto_refresh is False:
|
||||
self.refresh = False
|
||||
|
||||
return{'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_agregate_mesh)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_agregate_mesh)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
register()
|
|
@ -1,356 +0,0 @@
|
|||
# gpl author: Mano-Wii
|
||||
|
||||
bl_info = {
|
||||
"name": "Arrange on Curve",
|
||||
"author": "Mano-Wii",
|
||||
"version": (6, 3, 0),
|
||||
"blender": (2, 77, 0),
|
||||
"location": "3D View > Toolshelf > Create > Arrange on Curve",
|
||||
"description": "Arrange objects along a curve",
|
||||
"warning": "Select curve",
|
||||
"wiki_url": "",
|
||||
"category": "3D View"
|
||||
}
|
||||
|
||||
# Note: scene properties are moved into __init__
|
||||
# search for patterns advanced_objects and adv_obj
|
||||
|
||||
import bpy
|
||||
import mathutils
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
Panel,
|
||||
)
|
||||
from bpy.props import (
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
)
|
||||
|
||||
FLT_MIN = 0.004
|
||||
|
||||
|
||||
class PanelDupliCurve(Panel):
|
||||
bl_idname = "VIEW3D_PT_arranjar_numa_curva"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "TOOLS"
|
||||
bl_context = "objectmode"
|
||||
bl_category = "Create"
|
||||
bl_label = "Arrange on Curve"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object and context.mode == 'OBJECT' and context.object.type == 'CURVE'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
adv_obj = context.scene.advanced_objects
|
||||
|
||||
layout.prop(adv_obj, "arrange_c_use_selected")
|
||||
|
||||
if not adv_obj.arrange_c_use_selected:
|
||||
layout.prop(adv_obj, "arrange_c_select_type", expand=True)
|
||||
if adv_obj.arrange_c_select_type == 'O':
|
||||
layout.column(align=True).prop_search(
|
||||
adv_obj, "arrange_c_obj_arranjar",
|
||||
bpy.data, "objects"
|
||||
)
|
||||
elif adv_obj.arrange_c_select_type == 'G':
|
||||
layout.column(align=True).prop_search(
|
||||
adv_obj, "arrange_c_obj_arranjar",
|
||||
bpy.data, "collections"
|
||||
)
|
||||
if context.object.type == 'CURVE':
|
||||
layout.operator("object.arranjar_numa_curva", text="Arrange Objects")
|
||||
|
||||
|
||||
class DupliCurve(Operator):
|
||||
bl_idname = "object.arranjar_numa_curva"
|
||||
bl_label = "Arrange Objects along a Curve"
|
||||
bl_description = "Arange chosen / selected objects along the Active Curve"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
use_distance: EnumProperty(
|
||||
name="Arrangement",
|
||||
items=[
|
||||
("D", "Distance", "Objects are arranged depending on the distance", 0),
|
||||
("Q", "Quantity", "Objects are arranged depending on the quantity", 1),
|
||||
("R", "Range", "Objects are arranged uniformly between the corners", 2)
|
||||
]
|
||||
)
|
||||
distance: FloatProperty(
|
||||
name="Distance",
|
||||
description="Distance between Objects",
|
||||
default=1.0,
|
||||
min=FLT_MIN,
|
||||
soft_min=0.1,
|
||||
unit='LENGTH',
|
||||
)
|
||||
object_qt: IntProperty(
|
||||
name="Quantity",
|
||||
description="Object amount",
|
||||
default=2,
|
||||
min=0,
|
||||
)
|
||||
scale: FloatProperty(
|
||||
name="Scale",
|
||||
description="Object Scale",
|
||||
default=1.0,
|
||||
min=FLT_MIN,
|
||||
unit='LENGTH',
|
||||
)
|
||||
Yaw: FloatProperty(
|
||||
name="X",
|
||||
description="Rotate around the X axis (Yaw)",
|
||||
default=0.0,
|
||||
unit='ROTATION'
|
||||
)
|
||||
Pitch: FloatProperty(
|
||||
default=0.0,
|
||||
description="Rotate around the Y axis (Pitch)",
|
||||
name="Y",
|
||||
unit='ROTATION'
|
||||
)
|
||||
Roll: FloatProperty(
|
||||
default=0.0,
|
||||
description="Rotate around the Z axis (Roll)",
|
||||
name="Z",
|
||||
unit='ROTATION'
|
||||
)
|
||||
max_angle: FloatProperty(
|
||||
default=1.57079,
|
||||
max=3.141592,
|
||||
name="Angle",
|
||||
unit='ROTATION'
|
||||
)
|
||||
offset: FloatProperty(
|
||||
default=0.0,
|
||||
name="Offset",
|
||||
unit='LENGTH'
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.mode == 'OBJECT'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
col.prop(self, "use_distance", text="")
|
||||
col = layout.column(align=True)
|
||||
if self.use_distance == "D":
|
||||
col.prop(self, "distance")
|
||||
elif self.use_distance == "Q":
|
||||
col.prop(self, "object_qt")
|
||||
else:
|
||||
col.prop(self, "distance")
|
||||
col.prop(self, "max_angle")
|
||||
col.prop(self, "offset")
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.prop(self, "scale")
|
||||
col.prop(self, "Yaw")
|
||||
col.prop(self, "Pitch")
|
||||
col.prop(self, "Roll")
|
||||
|
||||
def Glpoints(self, curve):
|
||||
Gpoints = []
|
||||
for i, spline in enumerate(curve.data.splines):
|
||||
segments = len(spline.bezier_points)
|
||||
if segments >= 2:
|
||||
r = spline.resolution_u + 1
|
||||
|
||||
points = []
|
||||
for j in range(segments):
|
||||
bp1 = spline.bezier_points[j]
|
||||
inext = (j + 1)
|
||||
if inext == segments:
|
||||
if not spline.use_cyclic_u:
|
||||
break
|
||||
inext = 0
|
||||
bp2 = spline.bezier_points[inext]
|
||||
if bp1.handle_right_type == bp2.handle_left_type == 'VECTOR':
|
||||
_points = (bp1.co, bp2.co) if j == 0 else (bp2.co,)
|
||||
else:
|
||||
knot1 = bp1.co
|
||||
handle1 = bp1.handle_right
|
||||
handle2 = bp2.handle_left
|
||||
knot2 = bp2.co
|
||||
_points = mathutils.geometry.interpolate_bezier(knot1, handle1, handle2, knot2, r)
|
||||
points.extend(_points)
|
||||
Gpoints.append(tuple((curve.matrix_world * p for p in points)))
|
||||
elif len(spline.points) >= 2:
|
||||
l = [curve.matrix_world * p.co.xyz for p in spline.points]
|
||||
if spline.use_cyclic_u:
|
||||
l.append(l[0])
|
||||
Gpoints.append(tuple(l))
|
||||
|
||||
if self.use_distance == "R":
|
||||
max_angle = self.max_angle
|
||||
tmp_Gpoints = []
|
||||
sp = Gpoints[i]
|
||||
sp2 = [sp[0], sp[1]]
|
||||
lp = sp[1]
|
||||
v1 = lp - sp[0]
|
||||
for p in sp[2:]:
|
||||
v2 = p - lp
|
||||
try:
|
||||
if (3.14158 - v1.angle(v2)) < max_angle:
|
||||
tmp_Gpoints.append(tuple(sp2))
|
||||
sp2 = [lp]
|
||||
except Exception as e:
|
||||
print("\n[Add Advanced Objects]\nOperator: "
|
||||
"object.arranjar_numa_curva\nError: {}".format(e))
|
||||
pass
|
||||
sp2.append(p)
|
||||
v1 = v2
|
||||
lp = p
|
||||
tmp_Gpoints.append(tuple(sp2))
|
||||
Gpoints = Gpoints[:i] + tmp_Gpoints
|
||||
|
||||
lengths = []
|
||||
if self.use_distance != "D":
|
||||
for sp in Gpoints:
|
||||
lp = sp[1]
|
||||
leng = (lp - sp[0]).length
|
||||
for p in sp[2:]:
|
||||
leng += (p - lp).length
|
||||
lp = p
|
||||
lengths.append(leng)
|
||||
return Gpoints, lengths
|
||||
|
||||
def execute(self, context):
|
||||
if context.object.type != 'CURVE':
|
||||
return {'CANCELLED'}
|
||||
|
||||
curve = context.active_object
|
||||
Gpoints, lengs = self.Glpoints(curve)
|
||||
adv_obj = context.scene.advanced_objects
|
||||
|
||||
if adv_obj.arrange_c_use_selected:
|
||||
G_Objeto = context.selected_objects
|
||||
G_Objeto.remove(curve)
|
||||
|
||||
if not G_Objeto:
|
||||
return {'CANCELLED'}
|
||||
|
||||
elif adv_obj.arrange_c_select_type == 'O':
|
||||
G_Objeto = bpy.data.objects[adv_obj.arrange_c_obj_arranjar],
|
||||
elif adv_obj.arrange_c_select_type == 'G':
|
||||
G_Objeto = bpy.data.collections[adv_obj.arrange_c_obj_arranjar].objects
|
||||
|
||||
yawMatrix = mathutils.Matrix.Rotation(self.Yaw, 4, 'X')
|
||||
pitchMatrix = mathutils.Matrix.Rotation(self.Pitch, 4, 'Y')
|
||||
rollMatrix = mathutils.Matrix.Rotation(self.Roll, 4, 'Z')
|
||||
|
||||
max_angle = self.max_angle # max_angle is called in Glpoints
|
||||
|
||||
if self.use_distance == "D":
|
||||
dist = self.distance
|
||||
for sp_points in Gpoints:
|
||||
dx = 0.0 # Length of initial calculation of section
|
||||
last_point = sp_points[0]
|
||||
j = 0
|
||||
for point in sp_points[1:]:
|
||||
vetorx = point - last_point # Vector spline section
|
||||
quat = mathutils.Vector.to_track_quat(vetorx, 'X', 'Z') # Tracking the selected objects
|
||||
quat = quat.to_matrix().to_4x4()
|
||||
|
||||
v_len = vetorx.length
|
||||
if v_len > 0.0:
|
||||
dx += v_len # Defined length calculation equal total length of the spline section
|
||||
v_norm = vetorx / v_len
|
||||
while dx > dist:
|
||||
object = G_Objeto[j % len(G_Objeto)]
|
||||
j += 1
|
||||
dx -= dist # Calculating the remaining length of the section
|
||||
obj = object.copy()
|
||||
context.collection.objects.link(obj)
|
||||
obj.matrix_world = quat * yawMatrix * pitchMatrix * rollMatrix
|
||||
# Placing in the correct position
|
||||
obj.matrix_world.translation = point - v_norm * dx
|
||||
obj.scale *= self.scale
|
||||
last_point = point
|
||||
|
||||
elif self.use_distance == "Q":
|
||||
object_qt = self.object_qt + 1
|
||||
for i, sp_points in enumerate(Gpoints):
|
||||
dx = 0.0 # Length of initial calculation of section
|
||||
dist = lengs[i] / object_qt
|
||||
last_point = sp_points[0]
|
||||
j = 0
|
||||
for point in sp_points[1:]:
|
||||
vetorx = point - last_point # Vector spline section
|
||||
# Tracking the selected objects
|
||||
quat = mathutils.Vector.to_track_quat(vetorx, 'X', 'Z')
|
||||
quat = quat.to_matrix().to_4x4()
|
||||
|
||||
v_len = vetorx.length
|
||||
if v_len > 0.0:
|
||||
# Defined length calculation equal total length of the spline section
|
||||
dx += v_len
|
||||
v_norm = vetorx / v_len
|
||||
while dx > dist:
|
||||
object = G_Objeto[j % len(G_Objeto)]
|
||||
j += 1
|
||||
dx -= dist # Calculating the remaining length of the section
|
||||
obj = object.copy()
|
||||
context.collection.objects.link(obj)
|
||||
obj.matrix_world = quat * yawMatrix * pitchMatrix * rollMatrix
|
||||
# Placing in the correct position
|
||||
obj.matrix_world.translation = point - v_norm * dx
|
||||
obj.scale *= self.scale
|
||||
last_point = point
|
||||
|
||||
else:
|
||||
dist = self.distance
|
||||
offset2 = 2 * self.offset
|
||||
for i, sp_points in enumerate(Gpoints):
|
||||
leng = lengs[i] - offset2
|
||||
rest = leng % dist
|
||||
offset = offset2 + rest
|
||||
leng -= rest
|
||||
offset /= 2
|
||||
last_point = sp_points[0]
|
||||
|
||||
dx = dist - offset # Length of initial calculation of section
|
||||
j = 0
|
||||
for point in sp_points[1:]:
|
||||
vetorx = point - last_point # Vector spline section
|
||||
# Tracking the selected objects
|
||||
quat = mathutils.Vector.to_track_quat(vetorx, 'X', 'Z')
|
||||
quat = quat.to_matrix().to_4x4()
|
||||
|
||||
v_len = vetorx.length
|
||||
if v_len > 0.0:
|
||||
dx += v_len
|
||||
v_norm = vetorx / v_len
|
||||
while dx >= dist and leng >= 0.0:
|
||||
leng -= dist
|
||||
dx -= dist # Calculating the remaining length of the section
|
||||
object = G_Objeto[j % len(G_Objeto)]
|
||||
j += 1
|
||||
obj = object.copy()
|
||||
context.collection.objects.link(obj)
|
||||
obj.matrix_world = quat * yawMatrix * pitchMatrix * rollMatrix
|
||||
# Placing in the correct position
|
||||
obj.matrix_world.translation = point - v_norm * dx
|
||||
obj.scale *= self.scale
|
||||
last_point = point
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(PanelDupliCurve)
|
||||
bpy.utils.register_class(DupliCurve)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(PanelDupliCurve)
|
||||
bpy.utils.unregister_class(DupliCurve)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,166 +0,0 @@
|
|||
# gpl author: Antonis Karvelas
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
bl_info = {
|
||||
"name": "Circle Array",
|
||||
"author": "Antonis Karvelas",
|
||||
"version": (1, 0, 1),
|
||||
"blender": (2, 67, 0),
|
||||
"location": "View3D > Object > Circle_Array",
|
||||
"description": "Uses an existing array and creates an empty, "
|
||||
"rotates it properly and makes a Circle Array",
|
||||
"warning": "",
|
||||
"wiki_url": "",
|
||||
"category": "Mesh"
|
||||
}
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
from math import radians
|
||||
|
||||
|
||||
class Circle_Array(Operator):
|
||||
bl_label = "Circle Array"
|
||||
bl_idname = "objects.circle_array_operator"
|
||||
bl_description = ("Creates an Array Modifier with offset empty object\n"
|
||||
"Works with Mesh, Curve, Text and Surface\n"
|
||||
"Use an object with an existing Array modifier\n"
|
||||
"or rotate the newly created Empty with the name pattern\n"
|
||||
"EMPTY_C_Array_ if the Array doesn't exist (angle: 360/Count)")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object is not None
|
||||
|
||||
def check_empty_name(self, context):
|
||||
new_name, def_name = "", "EMPTY_C_Array"
|
||||
suffix = 1
|
||||
try:
|
||||
# first slap a simple linear count + 1 for numeric suffix, if it fails
|
||||
# harvest for the rightmost numbers and append the max value
|
||||
list_obj = []
|
||||
obj_all = context.scene.objects
|
||||
list_obj = [obj.name for obj in obj_all if obj.name.startswith(def_name)]
|
||||
new_name = "{}_{}".format(def_name, len(list_obj) + 1)
|
||||
|
||||
if new_name in list_obj:
|
||||
from re import findall
|
||||
test_num = [findall("\d+", words) for words in list_obj]
|
||||
suffix += max([int(l[-1]) for l in test_num])
|
||||
new_name = "{}_{}".format(def_name, suffix)
|
||||
return new_name
|
||||
except:
|
||||
return None
|
||||
|
||||
def execute(self, context):
|
||||
is_allowed = True
|
||||
try:
|
||||
allowed_obj = ['MESH', 'CURVE', 'SURFACE', 'FONT']
|
||||
for obj in context.selected_objects:
|
||||
if obj.type not in allowed_obj:
|
||||
is_allowed = False
|
||||
break
|
||||
|
||||
if not is_allowed:
|
||||
self.report(
|
||||
{"WARNING"},
|
||||
"The Active/Selected objects are not of "
|
||||
"Mesh, Curve, Surface or Font type. Operation Cancelled"
|
||||
)
|
||||
return {'CANCELLED'}
|
||||
|
||||
default_name = self.check_empty_name(context) or "EMPTY_C_Array"
|
||||
bpy.ops.object.modifier_add(type='ARRAY')
|
||||
|
||||
if len(context.selected_objects) == 2:
|
||||
selected = context.selected_objects
|
||||
lists = [obj for obj in selected if obj != context.active_object]
|
||||
active = lists[0]
|
||||
# check if the list object has a modifier
|
||||
check_mod = None
|
||||
for mod in active.modifiers[:]:
|
||||
if mod.type == "ARRAY":
|
||||
check_mod = mod
|
||||
break
|
||||
|
||||
if check_mod:
|
||||
check_mod.use_object_offset = True
|
||||
check_mod.use_relative_offset = False
|
||||
else:
|
||||
# fallback
|
||||
bpy.context.view_layer.objects.active = active
|
||||
bpy.ops.object.modifier_add(type='ARRAY')
|
||||
active.modifiers[0].use_object_offset = True
|
||||
active.modifiers[0].use_relative_offset = False
|
||||
|
||||
active.modifiers[0].use_object_offset = True
|
||||
active.modifiers[0].use_relative_offset = False
|
||||
active.select_set(False)
|
||||
bpy.context.view_layer.objects.active = context.active_object
|
||||
bpy.ops.view3d.snap_cursor_to_selected()
|
||||
|
||||
if active.modifiers[0].offset_object is None:
|
||||
bpy.ops.object.add(type='EMPTY')
|
||||
empty_name = bpy.context.active_object
|
||||
empty_name.name = default_name
|
||||
active.modifiers[0].offset_object = empty_name
|
||||
else:
|
||||
empty_name = active.modifiers[0].offset_object
|
||||
|
||||
bpy.context.view_layer.objects.active = active
|
||||
num = active.modifiers["Array"].count
|
||||
rotate_num = 360 / num
|
||||
active.select_set(True)
|
||||
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
|
||||
empty_name.rotation_euler = (0, 0, radians(rotate_num))
|
||||
empty_name.select_set(False)
|
||||
active.select_set(True)
|
||||
bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
|
||||
|
||||
return {'FINISHED'}
|
||||
else:
|
||||
active = context.active_object
|
||||
active.modifiers[0].use_object_offset = True
|
||||
active.modifiers[0].use_relative_offset = False
|
||||
bpy.ops.view3d.snap_cursor_to_selected()
|
||||
|
||||
if active.modifiers[0].offset_object is None:
|
||||
bpy.ops.object.add(type='EMPTY')
|
||||
empty_name = bpy.context.active_object
|
||||
empty_name.name = default_name
|
||||
active.modifiers[0].offset_object = empty_name
|
||||
else:
|
||||
empty_name = active.modifiers[0].offset_object
|
||||
|
||||
bpy.context.view_layer.objects.active = active
|
||||
num = active.modifiers["Array"].count
|
||||
rotate_num = 360 / num
|
||||
active.select_set(True)
|
||||
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
|
||||
empty_name.rotation_euler = (0, 0, radians(rotate_num))
|
||||
empty_name.select_set(False)
|
||||
active.select_set(True)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
except Exception as e:
|
||||
self.report({'WARNING'},
|
||||
"Circle Array operator could not be executed (See the console for more info)")
|
||||
print("\n[objects.circle_array_operator]\nError: {}\n".format(e))
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
# Register
|
||||
def register():
|
||||
bpy.utils.register_class(Circle_Array)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(Circle_Array)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,339 +0,0 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see http://www.gnu.org/licenses/
|
||||
# or write to the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
bl_info = {
|
||||
"name": "Copy2 Vertices, Edges or Faces",
|
||||
"author": "Eleanor Howick (elfnor.com)",
|
||||
"version": (0, 1, 1),
|
||||
"blender": (2, 71, 0),
|
||||
"location": "3D View > Object > Copy 2",
|
||||
"description": "Copy one object to the selected vertices, edges or faces of another object",
|
||||
"warning": "",
|
||||
"category": "Object"
|
||||
}
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
)
|
||||
from mathutils import (
|
||||
Vector,
|
||||
Matrix,
|
||||
)
|
||||
|
||||
|
||||
class Copy2(Operator):
|
||||
bl_idname = "mesh.copy2"
|
||||
bl_label = "Copy 2"
|
||||
bl_description = ("Copy Vertices, Edges or Faces to the Selected object\n"
|
||||
"Needs an existing Active Mesh Object")
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
obj_list = None
|
||||
|
||||
def obj_list_cb(self, context):
|
||||
return Copy2.obj_list
|
||||
|
||||
def sec_axes_list_cb(self, context):
|
||||
if self.priaxes == 'X':
|
||||
sec_list = [('Y', "Y", "Secondary axis Y"),
|
||||
('Z', "Z", "Secondary axis Z")]
|
||||
|
||||
if self.priaxes == 'Y':
|
||||
sec_list = [('X', "X", "Secondary axis X"),
|
||||
('Z', "Z", "Secondary axis Z")]
|
||||
|
||||
if self.priaxes == 'Z':
|
||||
sec_list = [('X', "X", "Secondary axis X"),
|
||||
('Y', "Y", "Secondary axis Y")]
|
||||
return sec_list
|
||||
|
||||
copytype: EnumProperty(
|
||||
items=(('V', "Vertex",
|
||||
"Paste the Copied Geometry to Vertices of the Active Object", 'VERTEXSEL', 0),
|
||||
('E', "Edge",
|
||||
"Paste the Copied Geometry to Edges of the Active Object", 'EDGESEL', 1),
|
||||
('F', "Face",
|
||||
"Paste the Copied Geometry to Faces of the Active Object", 'FACESEL', 2)),
|
||||
)
|
||||
copyfromobject: EnumProperty(
|
||||
name="Copy from",
|
||||
description="Copy an Object from the list",
|
||||
items=obj_list_cb
|
||||
)
|
||||
priaxes: EnumProperty(
|
||||
description="Primary axes used for Copied Object orientation",
|
||||
items=(('X', "X", "Along X"),
|
||||
('Y', "Y", "Along Y"),
|
||||
('Z', "Z", "Along Z")),
|
||||
)
|
||||
edgescale: BoolProperty(
|
||||
name="Scale to fill edge",
|
||||
default=False
|
||||
)
|
||||
secaxes: EnumProperty(
|
||||
name="Secondary Axis",
|
||||
description="Secondary axis used for Copied Object orientation",
|
||||
items=sec_axes_list_cb
|
||||
)
|
||||
scale: FloatProperty(
|
||||
name="Scale",
|
||||
default=1.0,
|
||||
min=0.0,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return obj and obj.type == "MESH"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.prop(self, "copyfromobject")
|
||||
layout.label(text="to:")
|
||||
layout.prop(self, "copytype", expand=True)
|
||||
layout.label(text="Primary axis:")
|
||||
layout.prop(self, "priaxes", expand=True)
|
||||
layout.label(text="Secondary axis:")
|
||||
layout.prop(self, "secaxes", expand=True)
|
||||
if self.copytype == "E":
|
||||
layout.prop(self, "edgescale")
|
||||
if self.edgescale:
|
||||
layout.prop(self, "scale")
|
||||
return
|
||||
|
||||
def execute(self, context):
|
||||
copytoobject = context.active_object.name
|
||||
axes = self.priaxes + self.secaxes
|
||||
|
||||
# check if there is a problem with the strings related to some chars
|
||||
copy_to_object = bpy.data.objects[copytoobject] if \
|
||||
copytoobject in bpy.data.objects else None
|
||||
|
||||
copy_from_object = bpy.data.objects[self.copyfromobject] if \
|
||||
self.copyfromobject in bpy.data.objects else None
|
||||
|
||||
if copy_to_object is None or copy_from_object is None:
|
||||
self.report({"WARNING"},
|
||||
"There was a problem with retrieving Object data. Operation Cancelled")
|
||||
return {"CANCELLED"}
|
||||
try:
|
||||
copy_to_from(
|
||||
context.collection,
|
||||
copy_to_object,
|
||||
copy_from_object,
|
||||
self.copytype,
|
||||
axes,
|
||||
self.edgescale,
|
||||
self.scale
|
||||
)
|
||||
except Exception as e:
|
||||
self.report({"WARNING"},
|
||||
"Copy2 could not be completed (Check the Console for more info)")
|
||||
print("\n[Add Advanced Objects]\nOperator: mesh.copy2\n{}\n".format(e))
|
||||
|
||||
return {"CANCELLED"}
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
Copy2.obj_list = [(obj.name, obj.name, obj.name) for obj in bpy.data.objects]
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def copy_to_from(collection, to_obj, from_obj, copymode, axes, edgescale, scale):
|
||||
if copymode == 'V':
|
||||
vertex_copy(collection, to_obj, from_obj, axes)
|
||||
|
||||
if copymode == 'E':
|
||||
# don't pass edgescalling to object types that cannot be scaled
|
||||
if from_obj.type in ["CAMERA", "LIGHT", "EMPTY", "ARMATURE", "SPEAKER", "META"]:
|
||||
edgescale = False
|
||||
edge_copy(collection, to_obj, from_obj, axes, edgescale, scale)
|
||||
|
||||
if copymode == 'F':
|
||||
face_copy(collection, to_obj, from_obj, axes)
|
||||
|
||||
|
||||
axes_dict = {'XY': (1, 2, 0),
|
||||
'XZ': (2, 1, 0),
|
||||
'YX': (0, 2, 1),
|
||||
'YZ': (2, 0, 1),
|
||||
'ZX': (0, 1, 2),
|
||||
'ZY': (1, 0, 2)}
|
||||
|
||||
|
||||
def copyto(collection, source_obj, pos, xdir, zdir, axes, scale=None):
|
||||
"""
|
||||
copy the source_obj to pos, so its primary axis points in zdir and its
|
||||
secondary axis points in xdir
|
||||
"""
|
||||
copy_obj = source_obj.copy()
|
||||
collection.objects.link(copy_obj)
|
||||
|
||||
xdir = xdir.normalized()
|
||||
zdir = zdir.normalized()
|
||||
# rotation first
|
||||
z_axis = zdir
|
||||
x_axis = xdir
|
||||
y_axis = z_axis.cross(x_axis)
|
||||
# use axes_dict to assign the axis as chosen in panel
|
||||
A, B, C = axes_dict[axes]
|
||||
rot_mat = Matrix()
|
||||
rot_mat[A].xyz = x_axis
|
||||
rot_mat[B].xyz = y_axis
|
||||
rot_mat[C].xyz = z_axis
|
||||
rot_mat.transpose()
|
||||
|
||||
# rotate object
|
||||
copy_obj.matrix_world = rot_mat
|
||||
|
||||
# move object into position
|
||||
copy_obj.location = pos
|
||||
|
||||
# scale object
|
||||
if scale is not None:
|
||||
copy_obj.scale = scale
|
||||
|
||||
return copy_obj
|
||||
|
||||
|
||||
def vertex_copy(collection, obj, source_obj, axes):
|
||||
# vertex select mode
|
||||
sel_verts = []
|
||||
copy_list = []
|
||||
|
||||
for v in obj.data.vertices:
|
||||
if v.select is True:
|
||||
sel_verts.append(v)
|
||||
|
||||
# make a set for each vertex. The set contains all the connected vertices
|
||||
# use sets so the list is unique
|
||||
vert_con = [set() for i in range(len(obj.data.vertices))]
|
||||
for e in obj.data.edges:
|
||||
vert_con[e.vertices[0]].add(e.vertices[1])
|
||||
vert_con[e.vertices[1]].add(e.vertices[0])
|
||||
|
||||
for v in sel_verts:
|
||||
pos = v.co * obj.matrix_world.transposed()
|
||||
xco = obj.data.vertices[list(vert_con[v.index])[0]].co * obj.matrix_world.transposed()
|
||||
|
||||
zdir = (v.co + v.normal) * obj.matrix_world.transposed() - pos
|
||||
zdir = zdir.normalized()
|
||||
|
||||
edir = pos - xco
|
||||
|
||||
# edir is nor perpendicular to z dir
|
||||
# want xdir to be projection of edir onto plane through pos with direction zdir
|
||||
xdir = edir - edir.dot(zdir) * zdir
|
||||
xdir = -xdir.normalized()
|
||||
|
||||
copy = copyto(collection, source_obj, pos, xdir, zdir, axes)
|
||||
copy_list.append(copy)
|
||||
|
||||
# select all copied objects
|
||||
for copy in copy_list:
|
||||
copy.select_set(True)
|
||||
obj.select_set(False)
|
||||
|
||||
|
||||
def edge_copy(collection, obj, source_obj, axes, es, scale):
|
||||
# edge select mode
|
||||
sel_edges = []
|
||||
copy_list = []
|
||||
|
||||
for e in obj.data.edges:
|
||||
if e.select is True:
|
||||
sel_edges.append(e)
|
||||
|
||||
for e in sel_edges:
|
||||
# pos is average of two edge vertexs
|
||||
v0 = obj.data.vertices[e.vertices[0]].co * obj.matrix_world.transposed()
|
||||
v1 = obj.data.vertices[e.vertices[1]].co * obj.matrix_world.transposed()
|
||||
pos = (v0 + v1) / 2
|
||||
# xdir is along edge
|
||||
xdir = v0 - v1
|
||||
xlen = xdir.magnitude
|
||||
xdir = xdir.normalized()
|
||||
# project each edge vertex normal onto plane normal to xdir
|
||||
vn0 = (obj.data.vertices[e.vertices[0]].co * obj.matrix_world.transposed() +
|
||||
obj.data.vertices[e.vertices[0]].normal) - v0
|
||||
vn1 = (obj.data.vertices[e.vertices[1]].co * obj.matrix_world.transposed() +
|
||||
obj.data.vertices[e.vertices[1]].normal) - v1
|
||||
vn0p = vn0 - vn0.dot(xdir) * xdir
|
||||
vn1p = vn1 - vn1.dot(xdir) * xdir
|
||||
# the mean of the two projected normals is the zdir
|
||||
zdir = vn0p + vn1p
|
||||
zdir = zdir.normalized()
|
||||
escale = None
|
||||
if es:
|
||||
escale = Vector([1.0, 1.0, 1.0])
|
||||
i = list('XYZ').index(axes[1])
|
||||
escale[i] = scale * xlen / source_obj.dimensions[i]
|
||||
|
||||
copy = copyto(collection, source_obj, pos, xdir, zdir, axes, scale=escale)
|
||||
copy_list.append(copy)
|
||||
|
||||
# select all copied objects
|
||||
for copy in copy_list:
|
||||
copy.select_set(True)
|
||||
obj.select_set(False)
|
||||
|
||||
|
||||
def face_copy(collection, obj, source_obj, axes):
|
||||
# face select mode
|
||||
sel_faces = []
|
||||
copy_list = []
|
||||
|
||||
for f in obj.data.polygons:
|
||||
if f.select is True:
|
||||
sel_faces.append(f)
|
||||
|
||||
for f in sel_faces:
|
||||
fco = f.center * obj.matrix_world.transposed()
|
||||
# get first vertex corner of transformed object
|
||||
vco = obj.data.vertices[f.vertices[0]].co * obj.matrix_world.transposed()
|
||||
# get face normal of transformed object
|
||||
fn = (f.center + f.normal) * obj.matrix_world.transposed() - fco
|
||||
fn = fn.normalized()
|
||||
|
||||
copy = copyto(collection, source_obj, fco, vco - fco, fn, axes)
|
||||
copy_list.append(copy)
|
||||
|
||||
# select all copied objects
|
||||
for copy in copy_list:
|
||||
copy.select_set(True)
|
||||
obj.select_set(False)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(Copy2)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(Copy2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,957 +0,0 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# Original Author = Jacob Morris
|
||||
# URL = blendingjacob.blogspot.com
|
||||
|
||||
# Note: scene properties are moved into __init__ together with the 3 update functions
|
||||
# for properties search for the name patterns adv_obj and advanced_objects
|
||||
|
||||
bl_info = {
|
||||
"name": "CubeSter",
|
||||
"author": "Jacob Morris",
|
||||
"version": (0, 7, 2),
|
||||
"blender": (2, 78, 0),
|
||||
"location": "View 3D > Toolbar > CubeSter",
|
||||
"description": "Takes image, image sequence, or audio file and converts it "
|
||||
"into a height map based on pixel color and alpha values",
|
||||
"category": "Add Mesh"
|
||||
}
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
Panel,
|
||||
)
|
||||
|
||||
import timeit
|
||||
from random import uniform
|
||||
from math import radians
|
||||
from os import (
|
||||
path,
|
||||
listdir,
|
||||
)
|
||||
|
||||
|
||||
# create block at center position x, y with block width 2 * hx and 2 * hy and height of h
|
||||
def create_block(x, y, hw, h, verts: list, faces: list):
|
||||
if bpy.context.scene.advanced_objects.cubester_block_style == "size":
|
||||
z = 0.0
|
||||
else:
|
||||
z = h
|
||||
h = 2 * hw
|
||||
|
||||
p = len(verts)
|
||||
verts += [(x - hw, y - hw, z), (x + hw, y - hw, z), (x + hw, y + hw, z), (x - hw, y + hw, z)]
|
||||
verts += [(x - hw, y - hw, z + h), (x + hw, y - hw, z + h),
|
||||
(x + hw, y + hw, z + h), (x - hw, y + hw, z + h)]
|
||||
|
||||
faces += [(p, p + 1, p + 5, p + 4), (p + 1, p + 2, p + 6, p + 5),
|
||||
(p + 2, p + 3, p + 7, p + 6), (p, p + 4, p + 7, p + 3),
|
||||
(p + 4, p + 5, p + 6, p + 7), (p, p + 3, p + 2, p + 1)]
|
||||
|
||||
|
||||
# go through all frames in len(frames), adjusting values at frames[x][y]
|
||||
def create_f_curves(mesh, frames, frame_step_size, style):
|
||||
# use data to animate mesh
|
||||
action = bpy.data.actions.new("CubeSterAnimation")
|
||||
|
||||
mesh.animation_data_create()
|
||||
mesh.animation_data.action = action
|
||||
|
||||
data_path = "vertices[%d].co"
|
||||
|
||||
vert_index = 4 if style == "blocks" else 0 # index of first vertex
|
||||
|
||||
# loop for every face height value
|
||||
for frame_start_vert in range(len(frames[0])):
|
||||
# only go once if plane, otherwise do all four vertices that are in top plane if blocks
|
||||
end_point = frame_start_vert + 4 if style == "blocks" else frame_start_vert + 1
|
||||
|
||||
# loop through to get the four vertices that compose the face
|
||||
for frame_vert in range(frame_start_vert, end_point):
|
||||
# fcurves for x, y, z
|
||||
fcurves = [action.fcurves.new(data_path % vert_index, i) for i in range(3)]
|
||||
frame_counter = 0 # go through each frame and add position
|
||||
temp_v = mesh.vertices[vert_index].co
|
||||
|
||||
# loop through frames
|
||||
for frame in frames:
|
||||
# new x, y, z positions
|
||||
vals = [temp_v[0], temp_v[1], frame[frame_start_vert]]
|
||||
for i in range(3): # for each x, y, z set each corresponding fcurve
|
||||
fcurves[i].keyframe_points.insert(frame_counter, vals[i], {'FAST'})
|
||||
|
||||
frame_counter += frame_step_size # skip frames for smoother animation
|
||||
|
||||
vert_index += 1
|
||||
|
||||
# only skip vertices if made of blocks
|
||||
if style == "blocks":
|
||||
vert_index += 4
|
||||
|
||||
|
||||
# create material with given name, apply to object
|
||||
def create_material(scene, ob, name):
|
||||
mat = bpy.data.materials.new("CubeSter_" + name)
|
||||
adv_obj = scene.advanced_objects
|
||||
image = None
|
||||
|
||||
# image
|
||||
if not adv_obj.cubester_use_image_color and adv_obj.cubester_color_image in bpy.data.images:
|
||||
try:
|
||||
image = bpy.data.images[adv_obj.cubester_color_image]
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
image = bpy.data.images[adv_obj.cubester_image]
|
||||
except:
|
||||
pass
|
||||
|
||||
if scene.render.engine == "CYCLES":
|
||||
mat.use_nodes = True
|
||||
nodes = mat.node_tree.nodes
|
||||
|
||||
att = nodes.new("ShaderNodeAttribute")
|
||||
att.attribute_name = "Col"
|
||||
att.location = (-200, 300)
|
||||
|
||||
att = nodes.new("ShaderNodeTexImage")
|
||||
if image:
|
||||
att.image = image
|
||||
|
||||
if adv_obj.cubester_load_type == "multiple":
|
||||
att.image.source = "SEQUENCE"
|
||||
att.location = (-200, 700)
|
||||
|
||||
att = nodes.new("ShaderNodeTexCoord")
|
||||
att.location = (-450, 600)
|
||||
|
||||
if adv_obj.cubester_materials == "image":
|
||||
mat.node_tree.links.new(
|
||||
nodes["Image Texture"].outputs[0],
|
||||
nodes["Diffuse BSDF"].inputs[0]
|
||||
)
|
||||
mat.node_tree.links.new(
|
||||
nodes["Texture Coordinate"].outputs[2],
|
||||
nodes["Image Texture"].inputs[0]
|
||||
)
|
||||
else:
|
||||
mat.node_tree.links.new(
|
||||
nodes["Attribute"].outputs[0],
|
||||
nodes["Diffuse BSDF"].inputs[0]
|
||||
)
|
||||
else:
|
||||
if adv_obj.cubester_materials == "image" or scene.render.engine != "BLENDER_RENDER":
|
||||
tex = bpy.data.textures.new("CubeSter_" + name, "IMAGE")
|
||||
if image:
|
||||
tex.image = image
|
||||
slot = mat.texture_slots.add()
|
||||
slot.texture = tex
|
||||
else:
|
||||
mat.use_vertex_color_paint = True
|
||||
|
||||
ob.data.materials.append(mat)
|
||||
|
||||
|
||||
# generate mesh from audio
|
||||
def create_mesh_from_audio(self, context, verts, faces):
|
||||
scene = context.scene
|
||||
view_layer = context.view_layer
|
||||
adv_obj = scene.advanced_objects
|
||||
audio_filepath = adv_obj.cubester_audio_path
|
||||
width = adv_obj.cubester_audio_width_blocks
|
||||
length = adv_obj.cubester_audio_length_blocks
|
||||
|
||||
size_per_hundred = adv_obj.cubester_size_per_hundred_pixels
|
||||
size = size_per_hundred / 100
|
||||
# Note: used for compatibility with vertex colors changes
|
||||
bl_version = bool(bpy.app.version >= (2, 79, 1))
|
||||
|
||||
# create all blocks
|
||||
y = -(width / 2) * size + (size / 2)
|
||||
for r in range(width):
|
||||
x = -(length / 2) * size + (size / 2)
|
||||
for c in range(length):
|
||||
create_block(x, y, size / 2, 1, verts, faces)
|
||||
|
||||
x += size
|
||||
y += size
|
||||
|
||||
# create object
|
||||
mesh = bpy.data.meshes.new("cubed")
|
||||
mesh.from_pydata(verts, [], faces)
|
||||
ob = bpy.data.objects.new("cubed", mesh)
|
||||
bpy.context.collection.objects.link(ob)
|
||||
bpy.context.view_layer.objects.active = ob
|
||||
ob.select_set(True)
|
||||
|
||||
# initial vertex colors
|
||||
if adv_obj.cubester_materials == "image" and adv_obj.cubester_color_image != "":
|
||||
picture = bpy.data.images[adv_obj.cubester_color_image]
|
||||
pixels = list(picture.pixels)
|
||||
vert_colors = []
|
||||
|
||||
skip_y = int(picture.size[1] / width)
|
||||
skip_x = int(picture.size[0] / length)
|
||||
|
||||
for row in range(0, picture.size[1], skip_y + 1):
|
||||
# go through each column, step by appropriate amount
|
||||
for column in range(0, picture.size[0] * 4, 4 + skip_x * 4):
|
||||
r, g, b, a = get_pixel_values(picture, pixels, row, column)
|
||||
get_colors = (r, g, b, a) if bl_version else (r, g, b)
|
||||
vert_colors += [get_colors for i in range(24)]
|
||||
|
||||
bpy.ops.mesh.vertex_color_add()
|
||||
|
||||
i = 0
|
||||
vert_colors_size = len(vert_colors)
|
||||
for c in ob.data.vertex_colors[0].data:
|
||||
if i < vert_colors_size:
|
||||
c.color = vert_colors[i]
|
||||
i += 1
|
||||
|
||||
# image sequence handling
|
||||
if adv_obj.cubester_load_type == "multiple":
|
||||
images = find_sequence_images(self, bpy.context)
|
||||
|
||||
frames_vert_colors = []
|
||||
|
||||
max_images = adv_obj.cubester_max_images + 1 if \
|
||||
len(images[0]) > adv_obj.cubester_max_images else len(images[0])
|
||||
|
||||
# goes through and for each image for each block finds new height
|
||||
for image_index in range(0, max_images, adv_obj.cubester_skip_images):
|
||||
filepath = images[0][image_index]
|
||||
name = images[1][image_index]
|
||||
picture = fetch_image(self, name, filepath)
|
||||
pixels = list(picture.pixels)
|
||||
|
||||
frame_colors = []
|
||||
|
||||
for row in range(0, picture.size[1], skip_y + 1):
|
||||
for column in range(0, picture.size[0] * 4, 4 + skip_x * 4):
|
||||
r, g, b, a = get_pixel_values(picture, pixels, row, column)
|
||||
get_colors = (r, g, b, a) if bl_version else (r, g, b)
|
||||
frame_colors += [get_colors for i in range(24)]
|
||||
|
||||
frames_vert_colors.append(frame_colors)
|
||||
|
||||
adv_obj.cubester_vertex_colors[ob.name] = \
|
||||
{"type": "vertex", "frames": frames_vert_colors,
|
||||
"frame_skip": adv_obj.cubester_frame_step,
|
||||
"total_images": max_images}
|
||||
|
||||
# either add material or create
|
||||
if ("CubeSter_" + "Vertex") in bpy.data.materials:
|
||||
ob.data.materials.append(bpy.data.materials["CubeSter_" + "Vertex"])
|
||||
else:
|
||||
create_material(scene, ob, "Vertex")
|
||||
|
||||
# set keyframe for each object as initial point
|
||||
frame = [1 for i in range(int(len(verts) / 8))]
|
||||
frames = [frame]
|
||||
|
||||
area = bpy.context.area
|
||||
old_type = area.type
|
||||
area.type = "GRAPH_EDITOR"
|
||||
|
||||
scene.frame_current = 0
|
||||
|
||||
create_f_curves(mesh, frames, 1, "blocks")
|
||||
|
||||
# deselect all fcurves
|
||||
fcurves = ob.data.animation_data.action.fcurves.data.fcurves
|
||||
for i in fcurves:
|
||||
i.select = False
|
||||
|
||||
max_images = adv_obj.cubester_audio_max_freq
|
||||
min_freq = adv_obj.cubester_audio_min_freq
|
||||
freq_frame = adv_obj.cubester_audio_offset_type
|
||||
|
||||
freq_step = (max_images - min_freq) / length
|
||||
freq_sub_step = freq_step / width
|
||||
|
||||
frame_step = adv_obj.cubester_audio_frame_offset
|
||||
|
||||
# animate each block with a portion of the frequency
|
||||
for c in range(length):
|
||||
frame_off = 0
|
||||
for r in range(width):
|
||||
if freq_frame == "frame":
|
||||
scene.frame_current = frame_off
|
||||
l = c * freq_step
|
||||
h = (c + 1) * freq_step
|
||||
frame_off += frame_step
|
||||
else:
|
||||
l = c * freq_step + (r * freq_sub_step)
|
||||
h = c * freq_step + ((r + 1) * freq_sub_step)
|
||||
|
||||
pos = c + (r * length) # block number
|
||||
index = pos * 4 # first index for vertex
|
||||
|
||||
# select curves
|
||||
for i in range(index, index + 4):
|
||||
curve = i * 3 + 2 # fcurve location
|
||||
fcurves[curve].select = True
|
||||
try:
|
||||
bpy.ops.graph.sound_bake(filepath=bpy.path.abspath(audio_filepath), low=l, high=h)
|
||||
except:
|
||||
pass
|
||||
|
||||
# deselect curves
|
||||
for i in range(index, index + 4):
|
||||
curve = i * 3 + 2 # fcurve location
|
||||
fcurves[curve].select = False
|
||||
|
||||
area.type = old_type
|
||||
|
||||
# UV unwrap
|
||||
create_uv_map(bpy.context, width, length)
|
||||
|
||||
# if radial apply needed modifiers
|
||||
if adv_obj.cubester_audio_block_layout == "radial":
|
||||
# add bezier curve of correct width
|
||||
bpy.ops.curve.primitive_bezier_circle_add()
|
||||
curve = bpy.context.object
|
||||
# slope determined off of collected data
|
||||
curve_size = (0.319 * (width * (size * 100)) - 0.0169) / 100
|
||||
curve.dimensions = (curve_size, curve_size, 0.0)
|
||||
# correct for z height
|
||||
curve.scale = (curve.scale[0], curve.scale[0], curve.scale[0])
|
||||
|
||||
ob.select_set(True)
|
||||
curve.select_set(False)
|
||||
view_layer.objects.active = ob
|
||||
|
||||
# data was collected and then multi-variable regression was done in Excel
|
||||
# influence of width and length
|
||||
width_infl, length_infl, intercept = -0.159125, 0.49996, 0.007637
|
||||
x_offset = ((width * (size * 100) * width_infl) +
|
||||
(length * (size * 100) * length_infl) + intercept) / 100
|
||||
ob.location = (ob.location[0] + x_offset, ob.location[1], ob.location[2])
|
||||
|
||||
ob.rotation_euler = (radians(-90), 0.0, 0.0)
|
||||
bpy.ops.object.modifier_add(type="CURVE")
|
||||
ob.modifiers["Curve"].object = curve
|
||||
ob.modifiers["Curve"].deform_axis = "POS_Z"
|
||||
|
||||
|
||||
# generate mesh from image(s)
|
||||
def create_mesh_from_image(self, scene, verts, faces):
|
||||
context = bpy.context
|
||||
adv_obj = scene.advanced_objects
|
||||
picture = bpy.data.images[adv_obj.cubester_image]
|
||||
pixels = list(picture.pixels)
|
||||
# Note: used for compatibility with vertex colors changes
|
||||
bl_version = bool(bpy.app.version >= (2, 79, 1))
|
||||
|
||||
x_pixels = picture.size[0] / (adv_obj.cubester_skip_pixels + 1)
|
||||
y_pixels = picture.size[1] / (adv_obj.cubester_skip_pixels + 1)
|
||||
|
||||
width = x_pixels / 100 * adv_obj.cubester_size_per_hundred_pixels
|
||||
height = y_pixels / 100 * adv_obj.cubester_size_per_hundred_pixels
|
||||
|
||||
step = width / x_pixels
|
||||
half_width = step / 2
|
||||
|
||||
y = -height / 2 + half_width
|
||||
|
||||
vert_colors = []
|
||||
rows = 0
|
||||
|
||||
# go through each row of pixels stepping by adv_obj.cubester_skip_pixels + 1
|
||||
for row in range(0, picture.size[1], adv_obj.cubester_skip_pixels + 1):
|
||||
rows += 1
|
||||
x = -width / 2 + half_width # reset to left edge of mesh
|
||||
# go through each column, step by appropriate amount
|
||||
for column in range(0, picture.size[0] * 4, 4 + adv_obj.cubester_skip_pixels * 4):
|
||||
r, g, b, a = get_pixel_values(picture, pixels, row, column)
|
||||
get_colors = (r, g, b, a) if bl_version else (r, g, b)
|
||||
h = find_point_height(r, g, b, a, scene)
|
||||
|
||||
# if not transparent
|
||||
if h != -1:
|
||||
if adv_obj.cubester_mesh_style == "blocks":
|
||||
create_block(x, y, half_width, h, verts, faces)
|
||||
vert_colors += [get_colors for i in range(24)]
|
||||
else:
|
||||
verts += [(x, y, h)]
|
||||
vert_colors += [get_colors for i in range(4)]
|
||||
|
||||
x += step
|
||||
y += step
|
||||
|
||||
# if plane not blocks, then remove last 4 items from vertex_colors
|
||||
# as the faces have already wrapped around
|
||||
if adv_obj.cubester_mesh_style == "plane":
|
||||
del vert_colors[len(vert_colors) - 4:len(vert_colors)]
|
||||
|
||||
# create faces if plane based and not block based
|
||||
if adv_obj.cubester_mesh_style == "plane":
|
||||
off = int(len(verts) / rows)
|
||||
for r in range(rows - 1):
|
||||
for c in range(off - 1):
|
||||
faces += [(r * off + c, r * off + c + 1, (r + 1) * off + c + 1, (r + 1) * off + c)]
|
||||
|
||||
mesh = bpy.data.meshes.new("cubed")
|
||||
mesh.from_pydata(verts, [], faces)
|
||||
ob = bpy.data.objects.new("cubed", mesh)
|
||||
context.collection.objects.link(ob)
|
||||
context.view_layer.objects.active = ob
|
||||
ob.select_set(True)
|
||||
|
||||
# uv unwrap
|
||||
if adv_obj.cubester_mesh_style == "blocks":
|
||||
create_uv_map(context, rows, int(len(faces) / 6 / rows))
|
||||
else:
|
||||
create_uv_map(context, rows - 1, int(len(faces) / (rows - 1)))
|
||||
|
||||
# material
|
||||
# determine name and if already created
|
||||
if adv_obj.cubester_materials == "vertex": # vertex color
|
||||
image_name = "Vertex"
|
||||
elif not adv_obj.cubester_use_image_color and \
|
||||
adv_obj.cubester_color_image in bpy.data.images and \
|
||||
adv_obj.cubester_materials == "image": # replaced image
|
||||
image_name = adv_obj.cubester_color_image
|
||||
else: # normal image
|
||||
image_name = adv_obj.cubester_image
|
||||
|
||||
# either add material or create
|
||||
if ("CubeSter_" + image_name) in bpy.data.materials:
|
||||
ob.data.materials.append(bpy.data.materials["CubeSter_" + image_name])
|
||||
|
||||
# create material
|
||||
else:
|
||||
create_material(scene, ob, image_name)
|
||||
|
||||
# vertex colors
|
||||
bpy.ops.mesh.vertex_color_add()
|
||||
i = 0
|
||||
for c in ob.data.vertex_colors[0].data:
|
||||
c.color = vert_colors[i]
|
||||
i += 1
|
||||
|
||||
frames = []
|
||||
# image sequence handling
|
||||
if adv_obj.cubester_load_type == "multiple":
|
||||
images = find_sequence_images(self, context)
|
||||
frames_vert_colors = []
|
||||
|
||||
max_images = adv_obj.cubester_max_images + 1 if \
|
||||
len(images[0]) > adv_obj.cubester_max_images else len(images[0])
|
||||
|
||||
# goes through and for each image for each block finds new height
|
||||
for image_index in range(0, max_images, adv_obj.cubester_skip_images):
|
||||
filepath = images[0][image_index]
|
||||
name = images[1][image_index]
|
||||
picture = fetch_image(self, name, filepath)
|
||||
pixels = list(picture.pixels)
|
||||
|
||||
frame_heights = []
|
||||
frame_colors = []
|
||||
|
||||
for row in range(0, picture.size[1], adv_obj.cubester_skip_pixels + 1):
|
||||
for column in range(0, picture.size[0] * 4, 4 + adv_obj.cubester_skip_pixels * 4):
|
||||
r, g, b, a = get_pixel_values(picture, pixels, row, column)
|
||||
get_colors = (r, g, b, a) if bl_version else (r, g, b)
|
||||
h = find_point_height(r, g, b, a, scene)
|
||||
|
||||
if h != -1:
|
||||
frame_heights.append(h)
|
||||
if adv_obj.cubester_mesh_style == "blocks":
|
||||
frame_colors += [get_colors for i in range(24)]
|
||||
else:
|
||||
frame_colors += [get_colors for i in range(4)]
|
||||
|
||||
if adv_obj.cubester_mesh_style == "plane":
|
||||
del vert_colors[len(vert_colors) - 4:len(vert_colors)]
|
||||
|
||||
frames.append(frame_heights)
|
||||
frames_vert_colors.append(frame_colors)
|
||||
|
||||
# determine what data to use
|
||||
if adv_obj.cubester_materials == "vertex" or scene.render.engine == "BLENDER_ENGINE":
|
||||
adv_obj.cubester_vertex_colors[ob.name] = {
|
||||
"type": "vertex", "frames": frames_vert_colors,
|
||||
"frame_skip": adv_obj.cubester_frame_step,
|
||||
"total_images": max_images
|
||||
}
|
||||
else:
|
||||
adv_obj.cubester_vertex_colors[ob.name] = {
|
||||
"type": "image", "frame_skip": adv_obj.cubester_frame_step,
|
||||
"total_images": max_images
|
||||
}
|
||||
att = get_image_node(ob.data.materials[0])
|
||||
att.image_user.frame_duration = len(frames) * adv_obj.cubester_frame_step
|
||||
|
||||
# animate mesh
|
||||
create_f_curves(
|
||||
mesh, frames,
|
||||
adv_obj.cubester_frame_step,
|
||||
adv_obj.cubester_mesh_style
|
||||
)
|
||||
|
||||
|
||||
# generate uv map for object
|
||||
def create_uv_map(context, rows, columns):
|
||||
adv_obj = context.scene.advanced_objects
|
||||
mesh = context.object.data
|
||||
mesh.uv_textures.new("cubester")
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
|
||||
uv_layer = bm.loops.layers.uv[0]
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
x_scale = 1 / columns
|
||||
y_scale = 1 / rows
|
||||
|
||||
y_pos = 0.0
|
||||
x_pos = 0.0
|
||||
count = columns - 1 # hold current count to compare to if need to go to next row
|
||||
|
||||
# if blocks
|
||||
if adv_obj.cubester_mesh_style == "blocks":
|
||||
for fa in range(int(len(bm.faces) / 6)):
|
||||
for i in range(6):
|
||||
pos = (fa * 6) + i
|
||||
bm.faces[pos].loops[0][uv_layer].uv = (x_pos, y_pos)
|
||||
bm.faces[pos].loops[1][uv_layer].uv = (x_pos + x_scale, y_pos)
|
||||
bm.faces[pos].loops[2][uv_layer].uv = (x_pos + x_scale, y_pos + y_scale)
|
||||
bm.faces[pos].loops[3][uv_layer].uv = (x_pos, y_pos + y_scale)
|
||||
|
||||
x_pos += x_scale
|
||||
|
||||
if fa >= count:
|
||||
y_pos += y_scale
|
||||
x_pos = 0.0
|
||||
count += columns
|
||||
|
||||
# if planes
|
||||
else:
|
||||
for fa in range(len(bm.faces)):
|
||||
bm.faces[fa].loops[0][uv_layer].uv = (x_pos, y_pos)
|
||||
bm.faces[fa].loops[1][uv_layer].uv = (x_pos + x_scale, y_pos)
|
||||
bm.faces[fa].loops[2][uv_layer].uv = (x_pos + x_scale, y_pos + y_scale)
|
||||
bm.faces[fa].loops[3][uv_layer].uv = (x_pos, y_pos + y_scale)
|
||||
|
||||
x_pos += x_scale
|
||||
|
||||
if fa >= count:
|
||||
y_pos += y_scale
|
||||
x_pos = 0.0
|
||||
count += columns
|
||||
|
||||
bm.to_mesh(mesh)
|
||||
|
||||
|
||||
# if already loaded return image, else load and return
|
||||
def fetch_image(self, name, load_path):
|
||||
if name in bpy.data.images:
|
||||
return bpy.data.images[name]
|
||||
else:
|
||||
try:
|
||||
image = bpy.data.images.load(load_path)
|
||||
return image
|
||||
except RuntimeError:
|
||||
self.report({"ERROR"}, "CubeSter: '{}' could not be loaded".format(load_path))
|
||||
return None
|
||||
|
||||
|
||||
# find height for point
|
||||
def find_point_height(r, g, b, a, scene):
|
||||
adv_obj = scene.advanced_objects
|
||||
if a: # if not completely transparent
|
||||
normalize = 1
|
||||
|
||||
# channel weighting
|
||||
if not adv_obj.cubester_advanced:
|
||||
composed = 0.25 * r + 0.25 * g + 0.25 * b + 0.25 * a
|
||||
else:
|
||||
# user defined weighting
|
||||
if not adv_obj.cubester_random_weights:
|
||||
composed = adv_obj.cubester_weight_r * r + adv_obj.cubester_weight_g * g + \
|
||||
adv_obj.cubester_weight_b * b + adv_obj.cubester_weight_a * a
|
||||
total = adv_obj.cubester_weight_r + adv_obj.cubester_weight_g + adv_obj.cubester_weight_b + \
|
||||
adv_obj.cubester_weight_a
|
||||
|
||||
normalize = 1 / total
|
||||
# random weighting
|
||||
else:
|
||||
weights = [uniform(0.0, 1.0) for i in range(4)]
|
||||
composed = weights[0] * r + weights[1] * g + weights[2] * b + weights[3] * a
|
||||
total = weights[0] + weights[1] + weights[2] + weights[3]
|
||||
normalize = 1 / total
|
||||
|
||||
if adv_obj.cubester_invert:
|
||||
h = (1 - composed) * adv_obj.cubester_height_scale * normalize
|
||||
else:
|
||||
h = composed * adv_obj.cubester_height_scale * normalize
|
||||
|
||||
return h
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
# find all images that would belong to sequence
|
||||
def find_sequence_images(self, context):
|
||||
scene = context.scene
|
||||
images = [[], []]
|
||||
|
||||
if scene.advanced_objects.cubester_image in bpy.data.images:
|
||||
image = bpy.data.images[scene.advanced_objects.cubester_image]
|
||||
main = image.name.split(".")[0]
|
||||
|
||||
# first part of name to check against other files
|
||||
length = len(main)
|
||||
keep_going = True
|
||||
for i in range(length - 1, -1, -1):
|
||||
if main[i].isdigit() and keep_going:
|
||||
length -= 1
|
||||
else:
|
||||
keep_going = not keep_going
|
||||
name = main[0:length]
|
||||
|
||||
dir_name = path.dirname(bpy.path.abspath(image.filepath))
|
||||
|
||||
try:
|
||||
for file in listdir(dir_name):
|
||||
if path.isfile(path.join(dir_name, file)) and file.startswith(name):
|
||||
images[0].append(path.join(dir_name, file))
|
||||
images[1].append(file)
|
||||
except FileNotFoundError:
|
||||
self.report({"ERROR"}, "CubeSter: '{}' directory not found".format(dir_name))
|
||||
|
||||
return images
|
||||
|
||||
|
||||
# get image node
|
||||
def get_image_node(mat):
|
||||
nodes = mat.node_tree.nodes
|
||||
att = nodes["Image Texture"]
|
||||
|
||||
return att
|
||||
|
||||
|
||||
# get the RGBA values from pixel
|
||||
def get_pixel_values(picture, pixels, row, column):
|
||||
# determine i position to start at based on row and column position
|
||||
i = (row * picture.size[0] * 4) + column
|
||||
pixs = pixels[i: i + 4]
|
||||
r = pixs[0]
|
||||
g = pixs[1]
|
||||
b = pixs[2]
|
||||
a = pixs[3]
|
||||
|
||||
return r, g, b, a
|
||||
|
||||
|
||||
# frame change handler for materials
|
||||
def material_frame_handler(scene):
|
||||
frame = scene.frame_current
|
||||
adv_obj = scene.advanced_objects
|
||||
|
||||
keys = list(adv_obj.cubester_vertex_colors.keys())
|
||||
|
||||
# get keys and see if object is still in scene
|
||||
for i in keys:
|
||||
# if object is in scene then update information
|
||||
if i in bpy.data.objects:
|
||||
ob = bpy.data.objects[i]
|
||||
data = adv_obj.advanced_objects.cubester_vertex_colors[ob.name]
|
||||
skip_frames = data["frame_skip"]
|
||||
|
||||
# update materials using vertex colors
|
||||
if data['type'] == "vertex":
|
||||
colors = data["frames"]
|
||||
|
||||
if frame % skip_frames == 0 and 0 <= frame < (data['total_images'] - 1) * skip_frames:
|
||||
use_frame = int(frame / skip_frames)
|
||||
color = colors[use_frame]
|
||||
|
||||
i = 0
|
||||
for c in ob.data.vertex_colors[0].data:
|
||||
c.color = color[i]
|
||||
i += 1
|
||||
|
||||
else:
|
||||
att = get_image_node(ob.data.materials[0])
|
||||
offset = frame - int(frame / skip_frames)
|
||||
att.image_user.frame_offset = -offset
|
||||
|
||||
# if the object is no longer in the scene then delete then entry
|
||||
else:
|
||||
del adv_obj.advanced_objects.cubester_vertex_colors[i]
|
||||
|
||||
|
||||
class CubeSterPanel(Panel):
|
||||
bl_idname = "OBJECT_PT_cubester"
|
||||
bl_label = "CubeSter"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "TOOLS"
|
||||
bl_category = "Create"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_context = "objectmode"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout.box()
|
||||
scene = bpy.context.scene
|
||||
adv_obj = scene.advanced_objects
|
||||
images_found = 0
|
||||
rows = 0
|
||||
columns = 0
|
||||
|
||||
layout.prop(adv_obj, "cubester_audio_image")
|
||||
|
||||
if adv_obj.cubester_audio_image == "image":
|
||||
box = layout.box()
|
||||
box.prop(adv_obj, "cubester_load_type")
|
||||
box.label(text="Image To Convert:")
|
||||
box.prop_search(adv_obj, "cubester_image", bpy.data, "images")
|
||||
box.prop(adv_obj, "cubester_load_image")
|
||||
|
||||
# find number of appropriate images if sequence
|
||||
if adv_obj.cubester_load_type == "multiple":
|
||||
box = layout.box()
|
||||
# display number of images found there
|
||||
images = find_sequence_images(self, context)
|
||||
images_found = len(images[0]) if len(images[0]) <= adv_obj.cubester_max_images \
|
||||
else adv_obj.cubester_max_images
|
||||
|
||||
if len(images[0]):
|
||||
box.label(str(len(images[0])) + " Images Found", icon="PACKAGE")
|
||||
|
||||
box.prop(adv_obj, "cubester_max_images")
|
||||
box.prop(adv_obj, "cubester_skip_images")
|
||||
box.prop(adv_obj, "cubester_frame_step")
|
||||
|
||||
box = layout.box()
|
||||
col = box.column(align=True)
|
||||
col.prop(adv_obj, "cubester_skip_pixels")
|
||||
col.prop(adv_obj, "cubester_size_per_hundred_pixels")
|
||||
col.prop(adv_obj, "cubester_height_scale")
|
||||
box.prop(adv_obj, "cubester_invert", icon="FILE_REFRESH")
|
||||
|
||||
box = layout.box()
|
||||
box.prop(adv_obj, "cubester_mesh_style", icon="MESH_GRID")
|
||||
|
||||
if adv_obj.cubester_mesh_style == "blocks":
|
||||
box.prop(adv_obj, "cubester_block_style")
|
||||
else:
|
||||
# audio file
|
||||
layout.prop(adv_obj, "cubester_audio_path")
|
||||
|
||||
box = layout.box()
|
||||
col = box.column(align=True)
|
||||
col.prop(adv_obj, "cubester_audio_min_freq")
|
||||
col.prop(adv_obj, "cubester_audio_max_freq")
|
||||
|
||||
box.separator()
|
||||
box.prop(adv_obj, "cubester_audio_offset_type")
|
||||
|
||||
if adv_obj.cubester_audio_offset_type == "frame":
|
||||
box.prop(adv_obj, "cubester_audio_frame_offset")
|
||||
box.prop(adv_obj, "cubester_audio_block_layout")
|
||||
box.separator()
|
||||
|
||||
col = box.column(align=True)
|
||||
col.prop(adv_obj, "cubester_audio_width_blocks")
|
||||
col.prop(adv_obj, "cubester_audio_length_blocks")
|
||||
|
||||
rows = adv_obj.cubester_audio_width_blocks
|
||||
columns = adv_obj.cubester_audio_length_blocks
|
||||
|
||||
col.prop(adv_obj, "cubester_size_per_hundred_pixels")
|
||||
|
||||
# materials
|
||||
box = layout.box()
|
||||
box.prop(adv_obj, "cubester_materials", icon="MATERIAL")
|
||||
|
||||
if adv_obj.cubester_materials == "image":
|
||||
box.prop(adv_obj, "cubester_load_type")
|
||||
|
||||
# find number of appropriate images if sequence
|
||||
if adv_obj.cubester_load_type == "multiple":
|
||||
# display number of images found there
|
||||
images = find_sequence_images(self, context)
|
||||
images_found = len(images[0]) if len(images[0]) <= adv_obj.cubester_max_images \
|
||||
else adv_obj.cubester_max_images
|
||||
|
||||
if len(images[0]):
|
||||
box.label(str(len(images[0])) + " Images Found", icon="PACKAGE")
|
||||
box.prop(adv_obj, "cubester_max_images")
|
||||
box.prop(adv_obj, "cubester_skip_images")
|
||||
box.prop(adv_obj, "cubester_frame_step")
|
||||
|
||||
box.separator()
|
||||
|
||||
if adv_obj.cubester_audio_image == "image":
|
||||
box.prop(adv_obj, "cubester_use_image_color", icon="COLOR")
|
||||
|
||||
if not adv_obj.cubester_use_image_color or adv_obj.cubester_audio_image == "audio":
|
||||
box.label(text="Image To Use For Colors:")
|
||||
box.prop_search(adv_obj, "cubester_color_image", bpy.data, "images")
|
||||
box.prop(adv_obj, "cubester_load_color_image")
|
||||
|
||||
if adv_obj.cubester_image in bpy.data.images:
|
||||
rows = int(bpy.data.images[adv_obj.cubester_image].size[1] /
|
||||
(adv_obj.cubester_skip_pixels + 1))
|
||||
columns = int(bpy.data.images[adv_obj.cubester_image].size[0] /
|
||||
(adv_obj.cubester_skip_pixels + 1))
|
||||
|
||||
box = layout.box()
|
||||
|
||||
if adv_obj.cubester_mesh_style == "blocks":
|
||||
box.label(text="Approximate Cube Count: " + str(rows * columns))
|
||||
box.label(text="Expected Verts/Faces: " + str(rows * columns * 8) + " / " + str(rows * columns * 6))
|
||||
else:
|
||||
box.label(text="Approximate Point Count: " + str(rows * columns))
|
||||
box.label(text="Expected Verts/Faces: " + str(rows * columns) + " / " + str(rows * (columns - 1)))
|
||||
|
||||
# blocks and plane generation time values
|
||||
if adv_obj.cubester_mesh_style == "blocks":
|
||||
slope = 0.0000876958
|
||||
intercept = 0.02501
|
||||
block_infl, frame_infl, intercept2 = 0.0025934, 0.38507, -0.5840189
|
||||
else:
|
||||
slope = 0.000017753
|
||||
intercept = 0.04201
|
||||
block_infl, frame_infl, intercept2 = 0.000619, 0.344636, -0.272759
|
||||
|
||||
# if creating image based mesh
|
||||
points = rows * columns
|
||||
if adv_obj.cubester_audio_image == "image":
|
||||
if adv_obj.cubester_load_type == "single":
|
||||
time = rows * columns * slope + intercept # approximate time count for mesh
|
||||
else:
|
||||
time = (points * slope) + intercept + (points * block_infl) + \
|
||||
(images_found / adv_obj.cubester_skip_images * frame_infl) + intercept2
|
||||
|
||||
box.label(text="Images To Be Used: " + str(int(images_found / adv_obj.cubester_skip_images)))
|
||||
else:
|
||||
# audio based mesh
|
||||
box.label(text="Audio Track Length: " + str(adv_obj.cubester_audio_file_length) + " frames")
|
||||
|
||||
block_infl, frame_infl, intercept = 0.0948, 0.0687566, -25.85985
|
||||
time = (points * block_infl) + (adv_obj.cubester_audio_file_length * frame_infl) + intercept
|
||||
if time < 0.0: # usually no audio loaded
|
||||
time = 0.0
|
||||
|
||||
time_mod = "s"
|
||||
if time > 60: # convert to minutes if needed
|
||||
time /= 60
|
||||
time_mod = "min"
|
||||
time = round(time, 3)
|
||||
|
||||
box.label(text="Expected Time: " + str(time) + " " + time_mod)
|
||||
|
||||
# advanced
|
||||
if adv_obj.cubester_audio_image == "image":
|
||||
icon_1 = "TRIA_DOWN" if adv_obj.cubester_advanced else "TRIA_RIGHT"
|
||||
# layout.separator()
|
||||
box = layout.box()
|
||||
box.prop(adv_obj, "cubester_advanced", icon=icon_1)
|
||||
|
||||
if adv_obj.cubester_advanced:
|
||||
box.prop(adv_obj, "cubester_random_weights", icon="RNDCURVE")
|
||||
|
||||
if not adv_obj.cubester_random_weights:
|
||||
box.label(text="RGBA Channel Weights", icon="COLOR")
|
||||
col = box.column(align=True)
|
||||
col.prop(adv_obj, "cubester_weight_r")
|
||||
col.prop(adv_obj, "cubester_weight_g")
|
||||
col.prop(adv_obj, "cubester_weight_b")
|
||||
col.prop(adv_obj, "cubester_weight_a")
|
||||
|
||||
# generate mesh
|
||||
layout.operator("mesh.cubester", icon="OBJECT_DATA")
|
||||
|
||||
|
||||
class CubeSter(Operator):
|
||||
bl_idname = "mesh.cubester"
|
||||
bl_label = "Generate CubeSter Mesh"
|
||||
bl_description = "Generate a mesh from an Image or Sound File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
verts, faces = [], []
|
||||
|
||||
start = timeit.default_timer()
|
||||
scene = bpy.context.scene
|
||||
adv_obj = scene.advanced_objects
|
||||
|
||||
if adv_obj.cubester_audio_image == "image":
|
||||
if adv_obj.cubester_image != "":
|
||||
create_mesh_from_image(self, scene, verts, faces)
|
||||
frames = find_sequence_images(self, context)
|
||||
created = len(frames[0])
|
||||
else:
|
||||
self.report({'WARNING'},
|
||||
"Please add an Image for Object generation. Operation Cancelled")
|
||||
return {"CANCELLED"}
|
||||
else:
|
||||
if (adv_obj.cubester_audio_path != "" and
|
||||
path.isfile(adv_obj.cubester_audio_path) and
|
||||
adv_obj.cubester_check_audio is True):
|
||||
|
||||
create_mesh_from_audio(self, context, verts, faces)
|
||||
created = adv_obj.cubester_audio_file_length
|
||||
else:
|
||||
self.report({'WARNING'},
|
||||
"Please add an Sound File for Object generation. Operation Cancelled")
|
||||
return {"CANCELLED"}
|
||||
|
||||
stop = timeit.default_timer()
|
||||
|
||||
if adv_obj.cubester_mesh_style == "blocks" or adv_obj.cubester_audio_image == "audio":
|
||||
self.report(
|
||||
{"INFO"},
|
||||
"CubeSter: {} blocks and {} frame(s) "
|
||||
"in {}s".format(str(int(len(verts) / 8)),
|
||||
str(created),
|
||||
str(round(stop - start, 4)))
|
||||
)
|
||||
else:
|
||||
self.report(
|
||||
{"INFO"},
|
||||
"CubeSter: {} points and {} frame(s) "
|
||||
"in {}s" .format(str(len(verts)),
|
||||
str(created),
|
||||
str(round(stop - start, 4)))
|
||||
)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
bpy.app.handlers.frame_change_pre.append(material_frame_handler)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
bpy.app.handlers.frame_change_pre.remove(material_frame_handler)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,592 +0,0 @@
|
|||
# Copyright (C) 2012 Bill Currie <bill@taniwha.org>
|
||||
# Date: 2012/2/20
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from bpy.types import Operator
|
||||
from bpy.props import (
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
BoolProperty,
|
||||
)
|
||||
from mathutils import (
|
||||
Vector,
|
||||
Matrix,
|
||||
Quaternion,
|
||||
)
|
||||
from math import (
|
||||
pi, cos,
|
||||
sin,
|
||||
)
|
||||
|
||||
cossin = []
|
||||
|
||||
# Initialize the cossin table based on the number of segments.
|
||||
#
|
||||
# @param n The number of segments into which the circle will be
|
||||
# divided.
|
||||
# @return None
|
||||
|
||||
|
||||
def build_cossin(n):
|
||||
global cossin
|
||||
cossin = []
|
||||
for i in range(n):
|
||||
a = 2 * pi * i / n
|
||||
cossin.append((cos(a), sin(a)))
|
||||
|
||||
|
||||
def select_up(axis):
|
||||
# if axis.length != 0 and (abs(axis[0] / axis.length) < 1e-5 and abs(axis[1] / axis.length) < 1e-5):
|
||||
if (abs(axis[0] / axis.length) < 1e-5 and abs(axis[1] / axis.length) < 1e-5):
|
||||
up = Vector((-1, 0, 0))
|
||||
else:
|
||||
up = Vector((0, 0, 1))
|
||||
return up
|
||||
|
||||
# Make a single strut in non-manifold mode.
|
||||
#
|
||||
# The strut will be a "cylinder" with @a n sides. The vertices of the
|
||||
# cylinder will be @a od / 2 from the center of the cylinder. Optionally,
|
||||
# extra loops will be placed (@a od - @a id) / 2 from either end. The
|
||||
# strut will be either a simple, open-ended single-surface "cylinder", or a
|
||||
# double walled "pipe" with the outer wall vertices @a od / 2 from the center
|
||||
# and the inner wall vertices @a id / 2 from the center. The two walls will
|
||||
# be joined together at the ends with a face ring such that the entire strut
|
||||
# is a manifold object. All faces of the strut will be quads.
|
||||
#
|
||||
# @param v1 Vertex representing one end of the strut's center-line.
|
||||
# @param v2 Vertex representing the other end of the strut's
|
||||
# center-line.
|
||||
# @param id The diameter of the inner wall of a solid strut. Used for
|
||||
# calculating the position of the extra loops irrespective
|
||||
# of the solidity of the strut.
|
||||
# @param od The diameter of the outer wall of a solid strut, or the
|
||||
# diameter of a non-solid strut.
|
||||
# @param solid If true, the strut will be made solid such that it has an
|
||||
# inner wall (diameter @a id), an outer wall (diameter
|
||||
# @a od), and face rings at either end of the strut such
|
||||
# the strut is a manifold object. If false, the strut is
|
||||
# a simple, open-ended "cylinder".
|
||||
# @param loops If true, edge loops will be placed at either end of the
|
||||
# strut, (@a od - @a id) / 2 from the end of the strut. The
|
||||
# loops make subsurfed solid struts work nicely.
|
||||
# @return A tuple containing a list of vertices and a list of faces.
|
||||
# The face vertex indices are accurate only for the list of
|
||||
# vertices for the created strut.
|
||||
|
||||
|
||||
def make_strut(v1, v2, ind, od, n, solid, loops):
|
||||
v1 = Vector(v1)
|
||||
v2 = Vector(v2)
|
||||
axis = v2 - v1
|
||||
pos = [(0, od / 2)]
|
||||
if loops:
|
||||
pos += [((od - ind) / 2, od / 2),
|
||||
(axis.length - (od - ind) / 2, od / 2)]
|
||||
pos += [(axis.length, od / 2)]
|
||||
if solid:
|
||||
pos += [(axis.length, ind / 2)]
|
||||
if loops:
|
||||
pos += [(axis.length - (od - ind) / 2, ind / 2),
|
||||
((od - ind) / 2, ind / 2)]
|
||||
pos += [(0, ind / 2)]
|
||||
vps = len(pos)
|
||||
fps = vps
|
||||
if not solid:
|
||||
fps -= 1
|
||||
fw = axis.copy()
|
||||
fw.normalize()
|
||||
up = select_up(axis)
|
||||
lf = up.cross(fw)
|
||||
lf.normalize()
|
||||
up = fw.cross(lf)
|
||||
mat = Matrix((fw, lf, up))
|
||||
mat.transpose()
|
||||
verts = [None] * n * vps
|
||||
faces = [None] * n * fps
|
||||
for i in range(n):
|
||||
base = (i - 1) * vps
|
||||
x = cossin[i][0]
|
||||
y = cossin[i][1]
|
||||
for j in range(vps):
|
||||
p = Vector((pos[j][0], pos[j][1] * x, pos[j][1] * y))
|
||||
p = mat * p
|
||||
verts[i * vps + j] = p + v1
|
||||
if i:
|
||||
for j in range(fps):
|
||||
f = (i - 1) * fps + j
|
||||
faces[f] = [base + j, base + vps + j,
|
||||
base + vps + (j + 1) % vps, base + (j + 1) % vps]
|
||||
base = len(verts) - vps
|
||||
i = n
|
||||
for j in range(fps):
|
||||
f = (i - 1) * fps + j
|
||||
faces[f] = [base + j, j, (j + 1) % vps, base + (j + 1) % vps]
|
||||
|
||||
return verts, faces
|
||||
|
||||
|
||||
# Project a point along a vector onto a plane.
|
||||
#
|
||||
# Really, just find the intersection of the line represented by @a point
|
||||
# and @a dir with the plane represented by @a norm and @a p. However, if
|
||||
# the point is on or in front of the plane, or the line is parallel to
|
||||
# the plane, the original point will be returned.
|
||||
#
|
||||
# @param point The point to be projected onto the plane.
|
||||
# @param dir The vector along which the point will be projected.
|
||||
# @param norm The normal of the plane onto which the point will be
|
||||
# projected.
|
||||
# @param p A point through which the plane passes.
|
||||
# @return A vector representing the projected point, or the
|
||||
# original point.
|
||||
|
||||
def project_point(point, dir, norm, p):
|
||||
d = (point - p).dot(norm)
|
||||
if d >= 0:
|
||||
# the point is already on or in front of the plane
|
||||
return point
|
||||
v = dir.dot(norm)
|
||||
if v * v < 1e-8:
|
||||
# the plane is unreachable
|
||||
return point
|
||||
return point - dir * d / v
|
||||
|
||||
|
||||
# Make a simple strut for debugging.
|
||||
#
|
||||
# The strut is just a single quad representing the Z axis of the edge.
|
||||
#
|
||||
# @param mesh The base mesh. Used for finding the edge vertices.
|
||||
# @param edge_num The number of the current edge. For the face vertex
|
||||
# indices.
|
||||
# @param edge The edge for which the strut will be built.
|
||||
# @param od Twice the width of the strut.
|
||||
# @return A tuple containing a list of vertices and a list of faces.
|
||||
# The face vertex indices are pre-adjusted by the edge
|
||||
# number.
|
||||
# @fixme The face vertex indices should be accurate for the local
|
||||
# vertices (consistency)
|
||||
|
||||
def make_debug_strut(mesh, edge_num, edge, od):
|
||||
v = [mesh.verts[edge.verts[0].index].co,
|
||||
mesh.verts[edge.verts[1].index].co,
|
||||
None, None]
|
||||
v[2] = v[1] + edge.z * od / 2
|
||||
v[3] = v[0] + edge.z * od / 2
|
||||
f = [[edge_num * 4 + 0, edge_num * 4 + 1,
|
||||
edge_num * 4 + 2, edge_num * 4 + 3]]
|
||||
return v, f
|
||||
|
||||
|
||||
# Make a cylinder with ends clipped to the end-planes of the edge.
|
||||
#
|
||||
# The strut is just a single quad representing the Z axis of the edge.
|
||||
#
|
||||
# @param mesh The base mesh. Used for finding the edge vertices.
|
||||
# @param edge_num The number of the current edge. For the face vertex
|
||||
# indices.
|
||||
# @param edge The edge for which the strut will be built.
|
||||
# @param od The diameter of the strut.
|
||||
# @return A tuple containing a list of vertices and a list of faces.
|
||||
# The face vertex indices are pre-adjusted by the edge
|
||||
# number.
|
||||
# @fixme The face vertex indices should be accurate for the local
|
||||
# vertices (consistency)
|
||||
|
||||
def make_clipped_cylinder(mesh, edge_num, edge, od):
|
||||
n = len(cossin)
|
||||
cyl = [None] * n
|
||||
v0 = mesh.verts[edge.verts[0].index].co
|
||||
c0 = v0 + od * edge.y
|
||||
v1 = mesh.verts[edge.verts[1].index].co
|
||||
c1 = v1 - od * edge.y
|
||||
for i in range(n):
|
||||
x = cossin[i][0]
|
||||
y = cossin[i][1]
|
||||
r = (edge.z * x - edge.x * y) * od / 2
|
||||
cyl[i] = [c0 + r, c1 + r]
|
||||
for p in edge.verts[0].planes:
|
||||
cyl[i][0] = project_point(cyl[i][0], edge.y, p, v0)
|
||||
for p in edge.verts[1].planes:
|
||||
cyl[i][1] = project_point(cyl[i][1], -edge.y, p, v1)
|
||||
v = [None] * n * 2
|
||||
f = [None] * n
|
||||
base = edge_num * n * 2
|
||||
for i in range(n):
|
||||
v[i * 2 + 0] = cyl[i][1]
|
||||
v[i * 2 + 1] = cyl[i][0]
|
||||
f[i] = [None] * 4
|
||||
f[i][0] = base + i * 2 + 0
|
||||
f[i][1] = base + i * 2 + 1
|
||||
f[i][2] = base + (i * 2 + 3) % (n * 2)
|
||||
f[i][3] = base + (i * 2 + 2) % (n * 2)
|
||||
return v, f
|
||||
|
||||
|
||||
# Represent a vertex in the base mesh, with additional information.
|
||||
#
|
||||
# These vertices are @b not shared between edges.
|
||||
#
|
||||
# @var index The index of the vert in the base mesh
|
||||
# @var edge The edge to which this vertex is attached.
|
||||
# @var edges A tuple of indicess of edges attached to this vert, not
|
||||
# including the edge to which this vertex is attached.
|
||||
# @var planes List of vectors representing the normals of the planes that
|
||||
# bisect the angle between this vert's edge and each other
|
||||
# adjacant edge.
|
||||
|
||||
class SVert:
|
||||
# Create a vertex holding additional information about the bmesh vertex.
|
||||
# @param bmvert The bmesh vertex for which additional information is
|
||||
# to be stored.
|
||||
# @param bmedge The edge to which this vertex is attached.
|
||||
|
||||
def __init__(self, bmvert, bmedge, edge):
|
||||
self.index = bmvert.index
|
||||
self.edge = edge
|
||||
edges = bmvert.link_edges[:]
|
||||
edges.remove(bmedge)
|
||||
self.edges = tuple(map(lambda e: e.index, edges))
|
||||
self.planes = []
|
||||
|
||||
def calc_planes(self, edges):
|
||||
for ed in self.edges:
|
||||
self.planes.append(calc_plane_normal(self.edge, edges[ed]))
|
||||
|
||||
|
||||
# Represent an edge in the base mesh, with additional information.
|
||||
#
|
||||
# Edges do not share vertices so that the edge is always on the front (back?
|
||||
# must verify) side of all the planes attached to its vertices. If the
|
||||
# vertices were shared, the edge could be on either side of the planes, and
|
||||
# there would be planes attached to the vertex that are irrelevant to the
|
||||
# edge.
|
||||
#
|
||||
# @var index The index of the edge in the base mesh.
|
||||
# @var bmedge Cached reference to this edge's bmedge
|
||||
# @var verts A tuple of 2 SVert vertices, one for each end of the
|
||||
# edge. The vertices are @b not shared between edges.
|
||||
# However, if two edges are connected via a vertex in the
|
||||
# bmesh, their corresponding SVert vertices will have the
|
||||
# the same index value.
|
||||
# @var x The x axis of the edges local frame of reference.
|
||||
# Initially invalid.
|
||||
# @var y The y axis of the edges local frame of reference.
|
||||
# Initialized such that the edge runs from verts[0] to
|
||||
# verts[1] along the negative y axis.
|
||||
# @var z The z axis of the edges local frame of reference.
|
||||
# Initially invalid.
|
||||
|
||||
|
||||
class SEdge:
|
||||
|
||||
def __init__(self, bmesh, bmedge):
|
||||
|
||||
self.index = bmedge.index
|
||||
self.bmedge = bmedge
|
||||
bmesh.verts.ensure_lookup_table()
|
||||
self.verts = (SVert(bmedge.verts[0], bmedge, self),
|
||||
SVert(bmedge.verts[1], bmedge, self))
|
||||
self.y = (bmesh.verts[self.verts[0].index].co -
|
||||
bmesh.verts[self.verts[1].index].co)
|
||||
self.y.normalize()
|
||||
self.x = self.z = None
|
||||
|
||||
def set_frame(self, up):
|
||||
self.x = self.y.cross(up)
|
||||
self.x.normalize()
|
||||
self.z = self.x.cross(self.y)
|
||||
|
||||
def calc_frame(self, base_edge):
|
||||
baxis = base_edge.y
|
||||
if (self.verts[0].index == base_edge.verts[0].index or
|
||||
self.verts[1].index == base_edge.verts[1].index):
|
||||
axis = -self.y
|
||||
elif (self.verts[0].index == base_edge.verts[1].index or
|
||||
self.verts[1].index == base_edge.verts[0].index):
|
||||
axis = self.y
|
||||
else:
|
||||
raise ValueError("edges not connected")
|
||||
if baxis.dot(axis) in (-1, 1):
|
||||
# aligned axis have their up/z aligned
|
||||
up = base_edge.z
|
||||
else:
|
||||
# Get the unit vector dividing the angle (theta) between baxis and
|
||||
# axis in two equal parts
|
||||
h = (baxis + axis)
|
||||
h.normalize()
|
||||
# (cos(theta/2), sin(theta/2) * n) where n is the unit vector of the
|
||||
# axis rotating baxis onto axis
|
||||
q = Quaternion([baxis.dot(h)] + list(baxis.cross(h)))
|
||||
# rotate the base edge's up around the rotation axis (blender
|
||||
# quaternion shortcut:)
|
||||
up = q * base_edge.z
|
||||
self.set_frame(up)
|
||||
|
||||
def calc_vert_planes(self, edges):
|
||||
for v in self.verts:
|
||||
v.calc_planes(edges)
|
||||
|
||||
def bisect_faces(self):
|
||||
n1 = self.bmedge.link_faces[0].normal
|
||||
if len(self.bmedge.link_faces) > 1:
|
||||
n2 = self.bmedge.link_faces[1].normal
|
||||
return (n1 + n2).normalized()
|
||||
return n1
|
||||
|
||||
def calc_simple_frame(self):
|
||||
return self.y.cross(select_up(self.y)).normalized()
|
||||
|
||||
def find_edge_frame(self, sedges):
|
||||
if self.bmedge.link_faces:
|
||||
return self.bisect_faces()
|
||||
if self.verts[0].edges or self.verts[1].edges:
|
||||
edges = list(self.verts[0].edges + self.verts[1].edges)
|
||||
for i in range(len(edges)):
|
||||
edges[i] = sedges[edges[i]]
|
||||
while edges and edges[-1].y.cross(self.y).length < 1e-3:
|
||||
edges.pop()
|
||||
if not edges:
|
||||
return self.calc_simple_frame()
|
||||
n1 = edges[-1].y.cross(self.y).normalized()
|
||||
edges.pop()
|
||||
while edges and edges[-1].y.cross(self.y).cross(n1).length < 1e-3:
|
||||
edges.pop()
|
||||
if not edges:
|
||||
return n1
|
||||
n2 = edges[-1].y.cross(self.y).normalized()
|
||||
return (n1 + n2).normalized()
|
||||
return self.calc_simple_frame()
|
||||
|
||||
|
||||
def calc_plane_normal(edge1, edge2):
|
||||
if edge1.verts[0].index == edge2.verts[0].index:
|
||||
axis1 = -edge1.y
|
||||
axis2 = edge2.y
|
||||
elif edge1.verts[1].index == edge2.verts[1].index:
|
||||
axis1 = edge1.y
|
||||
axis2 = -edge2.y
|
||||
elif edge1.verts[0].index == edge2.verts[1].index:
|
||||
axis1 = -edge1.y
|
||||
axis2 = -edge2.y
|
||||
elif edge1.verts[1].index == edge2.verts[0].index:
|
||||
axis1 = edge1.y
|
||||
axis2 = edge2.y
|
||||
else:
|
||||
raise ValueError("edges not connected")
|
||||
# Both axis1 and axis2 are unit vectors, so this will produce a vector
|
||||
# bisects the two, so long as they are not 180 degrees apart (in which
|
||||
# there are infinite solutions).
|
||||
return (axis1 + axis2).normalized()
|
||||
|
||||
|
||||
def build_edge_frames(edges):
|
||||
edge_set = set(edges)
|
||||
while edge_set:
|
||||
edge_queue = [edge_set.pop()]
|
||||
edge_queue[0].set_frame(edge_queue[0].find_edge_frame(edges))
|
||||
while edge_queue:
|
||||
current_edge = edge_queue.pop()
|
||||
for i in (0, 1):
|
||||
for e in current_edge.verts[i].edges:
|
||||
edge = edges[e]
|
||||
if edge.x is not None: # edge already processed
|
||||
continue
|
||||
edge_set.remove(edge)
|
||||
edge_queue.append(edge)
|
||||
edge.calc_frame(current_edge)
|
||||
|
||||
|
||||
def make_manifold_struts(truss_obj, od, segments):
|
||||
bpy.context.view_layer.objects.active = truss_obj
|
||||
bpy.ops.object.editmode_toggle()
|
||||
truss_mesh = bmesh.from_edit_mesh(truss_obj.data).copy()
|
||||
bpy.ops.object.editmode_toggle()
|
||||
edges = [None] * len(truss_mesh.edges)
|
||||
for i, e in enumerate(truss_mesh.edges):
|
||||
edges[i] = SEdge(truss_mesh, e)
|
||||
build_edge_frames(edges)
|
||||
verts = []
|
||||
faces = []
|
||||
for e, edge in enumerate(edges):
|
||||
# v, f = make_debug_strut(truss_mesh, e, edge, od)
|
||||
edge.calc_vert_planes(edges)
|
||||
v, f = make_clipped_cylinder(truss_mesh, e, edge, od)
|
||||
verts += v
|
||||
faces += f
|
||||
return verts, faces
|
||||
|
||||
|
||||
def make_simple_struts(truss_mesh, ind, od, segments, solid, loops):
|
||||
vps = 2
|
||||
if solid:
|
||||
vps *= 2
|
||||
if loops:
|
||||
vps *= 2
|
||||
fps = vps
|
||||
if not solid:
|
||||
fps -= 1
|
||||
|
||||
verts = [None] * len(truss_mesh.edges) * segments * vps
|
||||
faces = [None] * len(truss_mesh.edges) * segments * fps
|
||||
vbase = 0
|
||||
fbase = 0
|
||||
|
||||
for e in truss_mesh.edges:
|
||||
v1 = truss_mesh.vertices[e.vertices[0]]
|
||||
v2 = truss_mesh.vertices[e.vertices[1]]
|
||||
v, f = make_strut(v1.co, v2.co, ind, od, segments, solid, loops)
|
||||
for fv in f:
|
||||
for i in range(len(fv)):
|
||||
fv[i] += vbase
|
||||
for i in range(len(v)):
|
||||
verts[vbase + i] = v[i]
|
||||
for i in range(len(f)):
|
||||
faces[fbase + i] = f[i]
|
||||
# if not base % 12800:
|
||||
# print (base * 100 / len(verts))
|
||||
vbase += vps * segments
|
||||
fbase += fps * segments
|
||||
|
||||
return verts, faces
|
||||
|
||||
|
||||
def create_struts(self, context, ind, od, segments, solid, loops, manifold):
|
||||
build_cossin(segments)
|
||||
|
||||
for truss_obj in bpy.context.scene.objects:
|
||||
if not truss_obj.select_get():
|
||||
continue
|
||||
truss_obj.select_set(False)
|
||||
truss_mesh = truss_obj.to_mesh(context.scene, True, 'PREVIEW')
|
||||
if not truss_mesh.edges:
|
||||
continue
|
||||
if manifold:
|
||||
verts, faces = make_manifold_struts(truss_obj, od, segments)
|
||||
else:
|
||||
verts, faces = make_simple_struts(truss_mesh, ind, od, segments,
|
||||
solid, loops)
|
||||
mesh = bpy.data.meshes.new("Struts")
|
||||
mesh.from_pydata(verts, [], faces)
|
||||
obj = bpy.data.objects.new("Struts", mesh)
|
||||
bpy.context.collection.objects.link(obj)
|
||||
obj.select_set(True)
|
||||
obj.location = truss_obj.location
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
mesh.update()
|
||||
|
||||
|
||||
class Struts(Operator):
|
||||
bl_idname = "mesh.generate_struts"
|
||||
bl_label = "Struts"
|
||||
bl_description = ("Add one or more struts meshes based on selected truss meshes \n"
|
||||
"Note: can get very high poly\n"
|
||||
"Needs an existing Active Mesh Object")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
ind: FloatProperty(
|
||||
name="Inside Diameter",
|
||||
description="Diameter of inner surface",
|
||||
min=0.0, soft_min=0.0,
|
||||
max=100, soft_max=100,
|
||||
default=0.04
|
||||
)
|
||||
od: FloatProperty(
|
||||
name="Outside Diameter",
|
||||
description="Diameter of outer surface",
|
||||
min=0.001, soft_min=0.001,
|
||||
max=100, soft_max=100,
|
||||
default=0.05
|
||||
)
|
||||
manifold: BoolProperty(
|
||||
name="Manifold",
|
||||
description="Connect struts to form a single solid",
|
||||
default=False
|
||||
)
|
||||
solid: BoolProperty(
|
||||
name="Solid",
|
||||
description="Create inner surface",
|
||||
default=False
|
||||
)
|
||||
loops: BoolProperty(
|
||||
name="Loops",
|
||||
description="Create sub-surf friendly loops",
|
||||
default=False
|
||||
)
|
||||
segments: IntProperty(
|
||||
name="Segments",
|
||||
description="Number of segments around strut",
|
||||
min=3, soft_min=3,
|
||||
max=64, soft_max=64,
|
||||
default=12
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.prop(self, "ind")
|
||||
col.prop(self, "od")
|
||||
col.prop(self, "segments")
|
||||
col.separator()
|
||||
|
||||
col.prop(self, "manifold")
|
||||
col.prop(self, "solid")
|
||||
col.prop(self, "loops")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return obj is not None and obj.type == "MESH"
|
||||
|
||||
def execute(self, context):
|
||||
store_undo = bpy.context.preferences.edit.use_global_undo
|
||||
bpy.context.preferences.edit.use_global_undo = False
|
||||
keywords = self.as_keywords()
|
||||
|
||||
try:
|
||||
create_struts(self, context, **keywords)
|
||||
bpy.context.preferences.edit.use_global_undo = store_undo
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
except Exception as e:
|
||||
bpy.context.preferences.edit.use_global_undo = store_undo
|
||||
self.report({"WARNING"},
|
||||
"Make Struts could not be performed. Operation Cancelled")
|
||||
print("\n[mesh.generate_struts]\n{}".format(e))
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,361 +0,0 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
bl_info = {
|
||||
"name": "Easy Lattice Object",
|
||||
"author": "Kursad Karatas",
|
||||
"version": (0, 6, 0),
|
||||
"blender": (2, 66, 0),
|
||||
"location": "View3D > Easy Lattice",
|
||||
"description": "Create a lattice for shape editing",
|
||||
"warning": "",
|
||||
"wiki_url": "https://wiki.blender.org/index.php/Easy_Lattice_Editing_Addon",
|
||||
"tracker_url": "https://bitbucket.org/kursad/blender_addons_easylattice/src",
|
||||
"category": "Mesh",
|
||||
}
|
||||
|
||||
|
||||
import bpy
|
||||
from mathutils import (
|
||||
Matrix,
|
||||
Vector,
|
||||
)
|
||||
from bpy.types import Operator
|
||||
from bpy.props import (
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
)
|
||||
|
||||
|
||||
def createLattice(context, obj, props):
|
||||
# Create lattice and object
|
||||
lat = bpy.data.lattices.new('EasyLattice')
|
||||
ob = bpy.data.objects.new('EasyLattice', lat)
|
||||
|
||||
# Take into consideration any selected vertices (default: all vertices)
|
||||
selectedVertices = createVertexGroup(obj)
|
||||
|
||||
size, pos = findBBox(obj, selectedVertices)
|
||||
loc, rot = getTransformations(obj)
|
||||
|
||||
# the position comes from the bbox
|
||||
ob.location = pos
|
||||
|
||||
# the size from bbox * the incoming scale factor
|
||||
ob.scale = size * props[3]
|
||||
|
||||
# the rotation comes from the combined obj world
|
||||
# matrix which was converted to euler pairs
|
||||
ob.rotation_euler = buildRot_World(obj)
|
||||
ob.show_in_front = True
|
||||
|
||||
# Link object to scene
|
||||
scn = context.scene
|
||||
|
||||
# Take care of the local view
|
||||
base = scn.objects.link(ob)
|
||||
scn.objects.active = ob
|
||||
|
||||
v3d = None
|
||||
if context.space_data and context.space_data.type == 'VIEW_3D':
|
||||
v3d = context.space_data
|
||||
|
||||
if v3d and v3d.local_view:
|
||||
base.layers_from_view(v3d)
|
||||
|
||||
scn.update()
|
||||
|
||||
# Set lattice attributes
|
||||
lat.points_u = props[0]
|
||||
lat.points_v = props[1]
|
||||
lat.points_w = props[2]
|
||||
|
||||
lat.interpolation_type_u = props[4]
|
||||
lat.interpolation_type_v = props[4]
|
||||
lat.interpolation_type_w = props[4]
|
||||
|
||||
lat.use_outside = False
|
||||
|
||||
return ob
|
||||
|
||||
|
||||
def createVertexGroup(obj):
|
||||
vertices = obj.data.vertices
|
||||
selverts = []
|
||||
|
||||
if obj.mode == "EDIT":
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
group = obj.vertex_groups.new(name="easy_lattice_group")
|
||||
|
||||
for vert in vertices:
|
||||
if vert.select is True:
|
||||
selverts.append(vert)
|
||||
group.add([vert.index], 1.0, "REPLACE")
|
||||
|
||||
# Default: use all vertices
|
||||
if not selverts:
|
||||
for vert in vertices:
|
||||
selverts.append(vert)
|
||||
group.add([vert.index], 1.0, "REPLACE")
|
||||
|
||||
return selverts
|
||||
|
||||
|
||||
def getTransformations(obj):
|
||||
rot = obj.rotation_euler
|
||||
loc = obj.location
|
||||
|
||||
return [loc, rot]
|
||||
|
||||
|
||||
def findBBox(obj, selvertsarray):
|
||||
|
||||
mat = buildTrnScl_WorldMat(obj)
|
||||
mat_world = obj.matrix_world
|
||||
|
||||
minx, miny, minz = selvertsarray[0].co
|
||||
maxx, maxy, maxz = selvertsarray[0].co
|
||||
|
||||
c = 1
|
||||
|
||||
for c in range(len(selvertsarray)):
|
||||
co = selvertsarray[c].co
|
||||
|
||||
if co.x < minx:
|
||||
minx = co.x
|
||||
if co.y < miny:
|
||||
miny = co.y
|
||||
if co.z < minz:
|
||||
minz = co.z
|
||||
|
||||
if co.x > maxx:
|
||||
maxx = co.x
|
||||
if co.y > maxy:
|
||||
maxy = co.y
|
||||
if co.z > maxz:
|
||||
maxz = co.z
|
||||
c += 1
|
||||
|
||||
minpoint = Vector((minx, miny, minz))
|
||||
maxpoint = Vector((maxx, maxy, maxz))
|
||||
|
||||
# The middle position has to be calculated based on the real world matrix
|
||||
pos = ((minpoint + maxpoint) / 2)
|
||||
|
||||
minpoint = mat * minpoint # Calculate only based on loc/scale
|
||||
maxpoint = mat * maxpoint # Calculate only based on loc/scale
|
||||
pos = mat_world * pos # the middle position has to be calculated based on the real world matrix
|
||||
|
||||
size = maxpoint - minpoint
|
||||
size = Vector((max(0.1, abs(size.x)), max(0.1, abs(size.y)), max(0.1, abs(size.z)))) # Prevent zero size dimensions
|
||||
|
||||
return [size, pos]
|
||||
|
||||
|
||||
def buildTrnSclMat(obj):
|
||||
# This function builds a local matrix that encodes translation
|
||||
# and scale and it leaves out the rotation matrix
|
||||
# The rotation is applied at object level if there is any
|
||||
mat_trans = Matrix.Translation(obj.location)
|
||||
mat_scale = Matrix.Scale(obj.scale[0], 4, (1, 0, 0))
|
||||
mat_scale *= Matrix.Scale(obj.scale[1], 4, (0, 1, 0))
|
||||
mat_scale *= Matrix.Scale(obj.scale[2], 4, (0, 0, 1))
|
||||
|
||||
mat_final = mat_trans * mat_scale
|
||||
|
||||
return mat_final
|
||||
|
||||
|
||||
def buildTrnScl_WorldMat(obj):
|
||||
# This function builds a real world matrix that encodes translation
|
||||
# and scale and it leaves out the rotation matrix
|
||||
# The rotation is applied at object level if there is any
|
||||
loc, rot, scl = obj.matrix_world.decompose()
|
||||
mat_trans = Matrix.Translation(loc)
|
||||
|
||||
mat_scale = Matrix.Scale(scl[0], 4, (1, 0, 0))
|
||||
mat_scale *= Matrix.Scale(scl[1], 4, (0, 1, 0))
|
||||
mat_scale *= Matrix.Scale(scl[2], 4, (0, 0, 1))
|
||||
|
||||
mat_final = mat_trans * mat_scale
|
||||
|
||||
return mat_final
|
||||
|
||||
|
||||
# Feature use
|
||||
def buildRot_WorldMat(obj):
|
||||
# This function builds a real world matrix that encodes rotation
|
||||
# and it leaves out translation and scale matrices
|
||||
loc, rot, scl = obj.matrix_world.decompose()
|
||||
rot = rot.to_euler()
|
||||
|
||||
mat_rot = Matrix.Rotation(rot[0], 4, 'X')
|
||||
mat_rot *= Matrix.Rotation(rot[1], 4, 'Z')
|
||||
mat_rot *= Matrix.Rotation(rot[2], 4, 'Y')
|
||||
return mat_rot
|
||||
|
||||
|
||||
def buildTrn_WorldMat(obj):
|
||||
# This function builds a real world matrix that encodes translation
|
||||
# and scale and it leaves out the rotation matrix
|
||||
# The rotation is applied at object level if there is any
|
||||
loc, rot, scl = obj.matrix_world.decompose()
|
||||
mat_trans = Matrix.Translation(loc)
|
||||
|
||||
return mat_trans
|
||||
|
||||
|
||||
def buildScl_WorldMat(obj):
|
||||
# This function builds a real world matrix that encodes translation
|
||||
# and scale and it leaves out the rotation matrix
|
||||
# The rotation is applied at object level if there is any
|
||||
loc, rot, scl = obj.matrix_world.decompose()
|
||||
|
||||
mat_scale = Matrix.Scale(scl[0], 4, (1, 0, 0))
|
||||
mat_scale *= Matrix.Scale(scl[1], 4, (0, 1, 0))
|
||||
mat_scale *= Matrix.Scale(scl[2], 4, (0, 0, 1))
|
||||
|
||||
return mat_scale
|
||||
|
||||
|
||||
def buildRot_World(obj):
|
||||
# This function builds a real world rotation values
|
||||
loc, rot, scl = obj.matrix_world.decompose()
|
||||
rot = rot.to_euler()
|
||||
|
||||
return rot
|
||||
|
||||
|
||||
def main(context, lat_props):
|
||||
obj = context.object
|
||||
|
||||
if obj.type == "MESH":
|
||||
lat = createLattice(context, obj, lat_props)
|
||||
|
||||
modif = obj.modifiers.new("EasyLattice", "LATTICE")
|
||||
modif.object = lat
|
||||
modif.vertex_group = "easy_lattice_group"
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
bpy.ops.object.select_pattern(pattern=lat.name, extend=False)
|
||||
context.view_layer.objects.active = lat
|
||||
|
||||
context.scene.update()
|
||||
|
||||
return
|
||||
|
||||
|
||||
class EasyLattice(Operator):
|
||||
bl_idname = "object.easy_lattice"
|
||||
bl_label = "Easy Lattice Creator"
|
||||
bl_description = ("Create a Lattice modifier ready to edit\n"
|
||||
"Needs an existing Active Mesh Object\n")
|
||||
|
||||
lat_u: IntProperty(
|
||||
name="Lattice u",
|
||||
description="Points in u direction",
|
||||
default=3
|
||||
)
|
||||
lat_v: IntProperty(
|
||||
name="Lattice v",
|
||||
description="Points in v direction",
|
||||
default=3
|
||||
)
|
||||
lat_w: IntProperty(
|
||||
name="Lattice w",
|
||||
description="Points in w direction",
|
||||
default=3
|
||||
)
|
||||
lat_scale_factor: FloatProperty(
|
||||
name="Lattice scale factor",
|
||||
description="Adjustment to the lattice scale",
|
||||
default=1,
|
||||
min=0.1,
|
||||
step=1,
|
||||
precision=2
|
||||
)
|
||||
lat_types = (('KEY_LINEAR', "Linear", "Linear Interpolation type"),
|
||||
('KEY_CARDINAL', "Cardinal", "Cardinal Interpolation type"),
|
||||
('KEY_CATMULL_ROM', "Catmull-Rom", "Catmull-Rom Interpolation type"),
|
||||
('KEY_BSPLINE', "BSpline", "Key BSpline Interpolation Type")
|
||||
)
|
||||
lat_type: EnumProperty(
|
||||
name="Lattice Type",
|
||||
description="Choose Lattice Type",
|
||||
items=lat_types,
|
||||
default='KEY_BSPLINE'
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return obj is not None and obj.type == "MESH"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.prop(self, "lat_u")
|
||||
col.prop(self, "lat_v")
|
||||
col.prop(self, "lat_w")
|
||||
|
||||
layout.prop(self, "lat_scale_factor")
|
||||
|
||||
layout.prop(self, "lat_type")
|
||||
|
||||
def execute(self, context):
|
||||
lat_u = self.lat_u
|
||||
lat_v = self.lat_v
|
||||
lat_w = self.lat_w
|
||||
|
||||
lat_scale_factor = self.lat_scale_factor
|
||||
|
||||
# enum property no need to complicate things
|
||||
lat_type = self.lat_type
|
||||
# XXX, should use keyword args
|
||||
lat_props = [lat_u, lat_v, lat_w, lat_scale_factor, lat_type]
|
||||
try:
|
||||
main(context, lat_props)
|
||||
|
||||
except Exception as ex:
|
||||
print("\n[Add Advanced Objects]\nOperator:object.easy_lattice\n{}\n".format(ex))
|
||||
self.report(
|
||||
{'WARNING'},
|
||||
"Easy Lattice Creator could not be completed (See Console for more info)"
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(EasyLattice)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(EasyLattice)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,179 +0,0 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
bl_info = {
|
||||
"name": "Add Chain",
|
||||
"author": "Brian Hinton (Nichod)",
|
||||
"version": (0, 1, 2),
|
||||
"blender": (2, 71, 0),
|
||||
"location": "Toolshelf > Create Tab",
|
||||
"description": "Adds Chain with curve guide for easy creation",
|
||||
"warning": "",
|
||||
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
|
||||
"Scripts/Object/Add_Chain",
|
||||
"category": "Object",
|
||||
}
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
|
||||
|
||||
def Add_Chain():
|
||||
# Adds Empty to scene
|
||||
bpy.ops.object.add(
|
||||
type='EMPTY',
|
||||
view_align=False,
|
||||
enter_editmode=False,
|
||||
location=(0, 0, 0),
|
||||
rotation=(0, 0, 0),
|
||||
)
|
||||
|
||||
# Changes name of Empty to rot_link adds variable emp
|
||||
emp = bpy.context.object
|
||||
emp.name = "rot_link"
|
||||
|
||||
# Rotate emp ~ 90 degrees
|
||||
emp.rotation_euler = [1.570796, 0, 0]
|
||||
|
||||
# Adds Curve Path to scene
|
||||
bpy.ops.curve.primitive_nurbs_path_add(
|
||||
view_align=False,
|
||||
enter_editmode=False,
|
||||
location=(0, 0, 0),
|
||||
rotation=(0, 0, 0),
|
||||
)
|
||||
|
||||
# Change Curve name to deform adds variable curv
|
||||
curv = bpy.context.object
|
||||
curv.name = "deform"
|
||||
|
||||
# Inserts Torus primitive
|
||||
bpy.ops.mesh.primitive_torus_add(
|
||||
major_radius=1,
|
||||
minor_radius=0.25,
|
||||
major_segments=12,
|
||||
minor_segments=4,
|
||||
abso_major_rad=1,
|
||||
abso_minor_rad=0.5,
|
||||
)
|
||||
|
||||
# Positions Torus primitive to center of scene
|
||||
bpy.context.active_object.location = 0.0, 0.0, 0.0
|
||||
|
||||
# Resetting Torus rotation in case of 'Align to view' option enabled
|
||||
bpy.context.active_object.rotation_euler = 0.0, 0.0, 0.0
|
||||
|
||||
# Changes Torus name to chain adds variable tor
|
||||
tor = bpy.context.object
|
||||
tor.name = "chain"
|
||||
|
||||
# Adds Array Modifier to tor
|
||||
bpy.ops.object.modifier_add(type='ARRAY')
|
||||
|
||||
# Adds subsurf modifier tor
|
||||
bpy.ops.object.modifier_add(type='SUBSURF')
|
||||
|
||||
# Smooths tor
|
||||
bpy.ops.object.shade_smooth()
|
||||
|
||||
# Select curv
|
||||
sce = bpy.context.scene
|
||||
sce.objects.active = curv
|
||||
|
||||
# Toggle into editmode
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
# TODO, may be better to move objects directly
|
||||
# Translate curve object
|
||||
bpy.ops.transform.translate(
|
||||
value=(2, 0, 0),
|
||||
constraint_axis=(True, False, False),
|
||||
orient_type='GLOBAL',
|
||||
mirror=False,
|
||||
proportional='DISABLED',
|
||||
proportional_edit_falloff='SMOOTH',
|
||||
proportional_size=1,
|
||||
snap=False,
|
||||
snap_target='CLOSEST',
|
||||
snap_point=(0, 0, 0),
|
||||
snap_align=False,
|
||||
snap_normal=(0, 0, 0),
|
||||
release_confirm=False,
|
||||
)
|
||||
|
||||
# Toggle into objectmode
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
# Select tor or chain
|
||||
sce.objects.active = tor
|
||||
|
||||
# Selects Array Modifier for editing
|
||||
array = tor.modifiers['Array']
|
||||
|
||||
# Change Array Modifier Parameters
|
||||
array.fit_type = 'FIT_CURVE'
|
||||
array.curve = curv
|
||||
array.offset_object = emp
|
||||
array.use_object_offset = True
|
||||
array.relative_offset_displace = 0.549, 0.0, 0.0
|
||||
|
||||
# Add curve modifier
|
||||
bpy.ops.object.modifier_add(type='CURVE')
|
||||
|
||||
# Selects Curve Modifier for editing
|
||||
cur = tor.modifiers['Curve']
|
||||
|
||||
# Change Curve Modifier Parameters
|
||||
cur.object = curv
|
||||
|
||||
|
||||
class AddChain(Operator):
|
||||
bl_idname = "mesh.primitive_chain_add"
|
||||
bl_label = "Add Chain"
|
||||
bl_description = ("Create a Chain segment with helper objects controlling modifiers:\n"
|
||||
"1) A Curve Modifier Object (deform) for the length and shape,\n"
|
||||
"Edit the Path to extend Chain Length\n"
|
||||
"2) An Empty (rot_link) as an Array Offset for rotation")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
Add_Chain()
|
||||
|
||||
except Exception as e:
|
||||
self.report({'WARNING'},
|
||||
"Some operations could not be performed (See Console for more info)")
|
||||
|
||||
print("\n[Add Advanced Objects]\nOperator: "
|
||||
"mesh.primitive_chain_add\nError: {}".format(e))
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(AddChain)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(AddChain)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,288 +0,0 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# TODO: find English versions of created object names
|
||||
|
||||
bl_info = {
|
||||
"name": "Oscurart Chain Maker",
|
||||
"author": "Oscurart",
|
||||
"version": (1, 1),
|
||||
"blender": (2, 56, 0),
|
||||
"location": "Add > Mesh > Oscurart Chain",
|
||||
"description": "Create chain links from armatures",
|
||||
"warning": "",
|
||||
"wiki_url": "oscurart.blogspot.com",
|
||||
"category": "Object"}
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
FloatProperty,
|
||||
)
|
||||
from bpy.types import Operator
|
||||
|
||||
|
||||
def makeChain(self, context, mult, curverig):
|
||||
|
||||
if not context.active_object.type == 'ARMATURE':
|
||||
self.report({'WARNING'}, "Active Object must be an Armature")
|
||||
return False
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
VAR_SWITCH = abs(1)
|
||||
ARMATURE = bpy.context.active_object
|
||||
|
||||
def creahuesocero(hueso):
|
||||
# create data to link
|
||||
mesh = bpy.data.meshes.new("objectData" + str(hueso.name))
|
||||
object = bpy.data.objects.new("HardLink" + str(hueso.name), mesh)
|
||||
mesh.from_pydata(
|
||||
[(-0.04986128956079483, -0.6918092370033264, -0.17846597731113434),
|
||||
(-0.04986128956079483, -0.6918091773986816, 0.17846640944480896),
|
||||
(-0.049861326813697815, -0.154555082321167, 0.17846627533435822),
|
||||
(-0.049861326813697815, -0.15455523133277893, -0.17846614122390747),
|
||||
(-0.04986133798956871, -0.03475356101989746, 0.25805795192718506),
|
||||
(-0.04986133798956871, -0.03475397825241089, -0.25805795192718506),
|
||||
(-0.049861278384923935, -0.8116106986999512, -0.2580576539039612),
|
||||
(-0.049861278384923935, -0.8116104602813721, 0.25805822014808655),
|
||||
(-0.04986128211021423, -0.7692053318023682, 2.6668965347198537e-07),
|
||||
(-0.04986127093434334, -0.923523485660553, 2.7834033744511544e-07),
|
||||
(-0.04986133426427841, -0.0771591067314148, 3.5627678585115063e-08),
|
||||
(-0.04986134544014931, 0.0771591067314148, -3.5627678585115063e-08),
|
||||
(0.04986133798956871, -0.03475397825241089, -0.25805795192718506),
|
||||
(0.04986133053898811, 0.0771591067314148, -3.5627678585115063e-08),
|
||||
(0.04986133798956871, -0.03475356101989746, 0.25805795192718506),
|
||||
(0.04986134544014931, -0.15455523133277893, -0.17846614122390747),
|
||||
(0.04986134544014931, -0.0771591067314148, 3.5627678585115063e-08),
|
||||
(0.04986134544014931, -0.154555082321167, 0.17846627533435822),
|
||||
(0.049861397594213486, -0.8116106986999512, -0.2580576539039612),
|
||||
(0.04986140504479408, -0.923523485660553, 2.7834033744511544e-07),
|
||||
(0.049861397594213486, -0.8116104602813721, 0.25805822014808655),
|
||||
(0.04986139014363289, -0.6918091773986816, 0.17846640944480896),
|
||||
(0.04986139014363289, -0.7692053318023682, 2.6668965347198537e-07),
|
||||
(0.04986139014363289, -0.6918092370033264, -0.17846597731113434)],
|
||||
[(1, 2), (0, 3), (3, 5), (2, 4), (0, 6), (5, 6), (1, 7), (4, 7), (0, 8), (1, 8),
|
||||
(7, 9), (6, 9), (8, 9), (2, 10), (3, 10), (4, 11), (5, 11), (10, 11), (5, 12),
|
||||
(12, 13), (11, 13), (13, 14), (4, 14), (10, 16), (15, 16), (3, 15), (2, 17),
|
||||
(16, 17), (9, 19), (18, 19), (6, 18), (7, 20), (19, 20), (8, 22), (21, 22),
|
||||
(1, 21), (0, 23), (22, 23), (14, 20), (12, 18), (15, 23), (17, 21), (12, 15),
|
||||
(13, 16), (14, 17), (20, 21), (19, 22), (18, 23)],
|
||||
[(6, 0, 3, 5), (1, 7, 4, 2), (0, 6, 9, 8), (8, 9, 7, 1), (2, 4, 11, 10), (10, 11, 5, 3),
|
||||
(11, 13, 12, 5), (4, 14, 13, 11), (3, 15, 16, 10), (10, 16, 17, 2), (6, 18, 19, 9),
|
||||
(9, 19, 20, 7), (1, 21, 22, 8), (23, 0, 8, 22), (7, 20, 14, 4), (5, 12, 18, 6),
|
||||
(0, 23, 15, 3), (2, 17, 21, 1), (16, 15, 12, 13), (17, 16, 13, 14), (22, 21, 20, 19),
|
||||
(23, 22, 19, 18), (21, 17, 14, 20), (15, 23, 18, 12)]
|
||||
)
|
||||
mesh.validate()
|
||||
bpy.context.collection.objects.link(object)
|
||||
# scale to the bone
|
||||
bpy.data.objects["HardLink" + str(hueso.name)].scale = (hueso.length * mult,
|
||||
hueso.length * mult,
|
||||
hueso.length * mult)
|
||||
# Parent Objects
|
||||
bpy.data.objects["HardLink" + str(hueso.name)].parent = ARMATURE
|
||||
bpy.data.objects["HardLink" + str(hueso.name)].parent_type = 'BONE'
|
||||
bpy.data.objects["HardLink" + str(hueso.name)].parent_bone = hueso.name
|
||||
|
||||
def creahuesonoventa(hueso):
|
||||
# create data to link
|
||||
mesh = bpy.data.meshes.new("objectData" + str(hueso.name))
|
||||
object = bpy.data.objects.new("NewLink" + str(hueso.name), mesh)
|
||||
mesh.from_pydata(
|
||||
[(0.1784660965204239, -0.6918091773986816, -0.049861203879117966),
|
||||
(-0.1784662902355194, -0.6918091773986816, -0.04986126348376274),
|
||||
(-0.17846627533435822, -0.1545550525188446, -0.04986134544014931),
|
||||
(0.17846617102622986, -0.15455520153045654, -0.04986128583550453),
|
||||
(-0.25805795192718506, -0.03475359082221985, -0.049861375242471695),
|
||||
(0.25805795192718506, -0.034753888845443726, -0.04986129328608513),
|
||||
(0.2580578327178955, -0.8116105794906616, -0.04986117407679558),
|
||||
(-0.2580580413341522, -0.8116105198860168, -0.049861256033182144),
|
||||
(-9.672299938756623e-08, -0.7692052721977234, -0.04986122250556946),
|
||||
(-8.99775329799013e-08, -0.923523485660553, -0.04986120015382767),
|
||||
(-7.764004550381287e-09, -0.07715904712677002, -0.049861326813697815),
|
||||
(4.509517737005808e-08, 0.0771591067314148, -0.049861349165439606),
|
||||
(0.25805795192718506, -0.034753888845443726, 0.049861375242471695),
|
||||
(-2.2038317837314025e-08, 0.0771591067314148, 0.049861326813697815),
|
||||
(-0.25805795192718506, -0.03475359082221985, 0.04986129328608513),
|
||||
(0.17846617102622986, -0.15455520153045654, 0.04986138269305229),
|
||||
(-1.529285498236277e-08, -0.07715907692909241, 0.049861352890729904),
|
||||
(-0.17846627533435822, -0.1545550525188446, 0.049861323088407516),
|
||||
(0.2580578029155731, -0.8116105794906616, 0.049861494451761246),
|
||||
(-1.5711103173998708e-07, -0.923523485660553, 0.04986147582530975),
|
||||
(-0.2580580711364746, -0.8116105198860168, 0.04986141249537468),
|
||||
(-0.1784663051366806, -0.6918091773986816, 0.049861419945955276),
|
||||
(-1.340541757599567e-07, -0.7692052721977234, 0.049861449748277664),
|
||||
(0.1784660816192627, -0.6918091773986816, 0.04986146464943886)],
|
||||
[(1, 2), (0, 3), (3, 5), (2, 4), (0, 6), (5, 6), (1, 7), (4, 7), (0, 8),
|
||||
(1, 8), (7, 9), (6, 9), (8, 9), (2, 10), (3, 10), (4, 11), (5, 11), (10, 11),
|
||||
(5, 12), (12, 13), (11, 13), (13, 14), (4, 14), (10, 16), (15, 16), (3, 15),
|
||||
(2, 17), (16, 17), (9, 19), (18, 19), (6, 18), (7, 20), (19, 20), (8, 22),
|
||||
(21, 22), (1, 21), (0, 23), (22, 23), (14, 20), (12, 18), (15, 23), (17, 21),
|
||||
(12, 15), (13, 16), (14, 17), (20, 21), (19, 22), (18, 23)],
|
||||
[(6, 0, 3, 5), (1, 7, 4, 2), (0, 6, 9, 8), (8, 9, 7, 1), (2, 4, 11, 10),
|
||||
(10, 11, 5, 3), (11, 13, 12, 5), (4, 14, 13, 11), (3, 15, 16, 10), (10, 16, 17, 2),
|
||||
(6, 18, 19, 9), (9, 19, 20, 7), (1, 21, 22, 8), (23, 0, 8, 22), (7, 20, 14, 4),
|
||||
(5, 12, 18, 6), (0, 23, 15, 3), (2, 17, 21, 1), (16, 15, 12, 13), (17, 16, 13, 14),
|
||||
(22, 21, 20, 19), (23, 22, 19, 18), (21, 17, 14, 20), (15, 23, 18, 12)]
|
||||
)
|
||||
mesh.validate()
|
||||
bpy.context.collection.objects.link(object)
|
||||
# scale to the bone
|
||||
bpy.data.objects["NewLink" + str(hueso.name)].scale = (hueso.length * mult,
|
||||
hueso.length * mult,
|
||||
hueso.length * mult)
|
||||
# Parent objects
|
||||
bpy.data.objects["NewLink" + str(hueso.name)].parent = ARMATURE
|
||||
bpy.data.objects["NewLink" + str(hueso.name)].parent_type = 'BONE'
|
||||
bpy.data.objects["NewLink" + str(hueso.name)].parent_bone = hueso.name
|
||||
|
||||
for hueso in bpy.context.active_object.pose.bones:
|
||||
if VAR_SWITCH == 1:
|
||||
creahuesocero(hueso)
|
||||
else:
|
||||
creahuesonoventa(hueso)
|
||||
if VAR_SWITCH == 1:
|
||||
VAR_SWITCH = 0
|
||||
else:
|
||||
VAR_SWITCH = 1
|
||||
|
||||
# if curve rig is activated
|
||||
if curverig is True:
|
||||
# variables
|
||||
LISTA_POINTC = []
|
||||
ACTARM = bpy.context.active_object
|
||||
|
||||
# create data and link the object to the scene
|
||||
crv = bpy.data.curves.new("CurvaCable", "CURVE")
|
||||
obCable = bpy.data.objects.new("Cable", crv)
|
||||
bpy.context.collection.objects.link(obCable)
|
||||
|
||||
# set the attributes
|
||||
crv.dimensions = "3D"
|
||||
crv.resolution_u = 10
|
||||
crv.resolution_v = 10
|
||||
crv.twist_mode = "MINIMUM"
|
||||
|
||||
# create the list of tail and head coordinates
|
||||
LISTA_POINTC.append((
|
||||
ACTARM.data.bones[0].head_local[0],
|
||||
ACTARM.data.bones[0].head_local[1],
|
||||
ACTARM.data.bones[0].head_local[2], 1
|
||||
))
|
||||
|
||||
for hueso in ACTARM.data.bones:
|
||||
LISTA_POINTC.append((
|
||||
hueso.tail_local[0],
|
||||
hueso.tail_local[1],
|
||||
hueso.tail_local[2], 1
|
||||
))
|
||||
|
||||
# create the Spline
|
||||
spline = crv.splines.new("NURBS")
|
||||
lencoord = len(LISTA_POINTC)
|
||||
rango = range(lencoord)
|
||||
spline.points.add(lencoord - 1)
|
||||
|
||||
for punto in rango:
|
||||
spline.points[punto].co = LISTA_POINTC[punto]
|
||||
|
||||
# set the endpoint
|
||||
bpy.data.objects['Cable'].data.splines[0].use_endpoint_u = True
|
||||
# select the curve
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
bpy.data.objects['Cable'].select = 1
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects['Cable']
|
||||
# switch to Edit mode
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
# create hooks
|
||||
POINTSTEP = 0
|
||||
for POINT in bpy.data.objects['Cable'].data.splines[0].points:
|
||||
bpy.ops.curve.select_all(action="DESELECT")
|
||||
bpy.data.objects['Cable'].data.splines[0].points[POINTSTEP].select = 1
|
||||
bpy.ops.object.hook_add_newob()
|
||||
POINTSTEP += 1
|
||||
|
||||
# Objects selection step
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
ACTARM.select = 1
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects['Armature']
|
||||
bpy.ops.object.mode_set(mode='POSE')
|
||||
bpy.ops.pose.select_all(action='DESELECT')
|
||||
ACTARM.data.bones[-1].select = 1
|
||||
ACTARM.data.bones.active = ACTARM.data.bones[-1]
|
||||
|
||||
# set IK Spline
|
||||
bpy.ops.pose.constraint_add_with_targets(type='SPLINE_IK')
|
||||
ACTARM.pose.bones[-1].constraints['Spline IK'].target = bpy.data.objects['Cable']
|
||||
ACTARM.pose.bones[-1].constraints['Spline IK'].chain_count = 100
|
||||
bpy.context.active_object.pose.bones[-1].constraints['Spline IK'].use_y_stretch = False
|
||||
# return to Object mode
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
|
||||
class MESH_OT_primitive_oscurart_chain_add(Operator):
|
||||
bl_idname = "mesh.primitive_oscurart_chain_add"
|
||||
bl_label = "Chain to Bones"
|
||||
bl_description = ("Add Chain Parented to an Existing Armature\n"
|
||||
"The Active/Last Selected Object must be an Armature")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
curverig: BoolProperty(
|
||||
name="Curve Rig",
|
||||
default=False
|
||||
)
|
||||
multiplier: FloatProperty(
|
||||
name="Scale",
|
||||
default=1,
|
||||
min=0.01, max=100.0
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return (obj is not None and obj.type == "ARMATURE")
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
makeChain(self, context, self.multiplier, self.curverig)
|
||||
|
||||
except Exception as e:
|
||||
self.report({'WARNING'},
|
||||
"Some operations could not be performed (See Console for more info)")
|
||||
|
||||
print("\n[Add Advanced Objects]\nOperator: "
|
||||
"mesh.primitive_oscurart_chain_add\nError: {}".format(e))
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(MESH_OT_primitive_oscurart_chain_add)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(MESH_OT_primitive_oscurart_chain_add)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,137 +0,0 @@
|
|||
# gpl author: liero
|
||||
# very simple 'pixelization' or 'voxelization' engine #
|
||||
|
||||
bl_info = {
|
||||
"name": "3D Pixelate",
|
||||
"author": "liero",
|
||||
"version": (0, 5, 3),
|
||||
"blender": (2, 74, 0),
|
||||
"location": "View3D > Tool Shelf",
|
||||
"description": "Creates a 3d pixelated version of the object",
|
||||
"category": "Object"}
|
||||
|
||||
# Note: winmgr properties are moved to the operator
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
from bpy.props import (
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
)
|
||||
|
||||
|
||||
def pix(self, obj):
|
||||
sce = bpy.context.scene
|
||||
obj.hide = obj.hide_render = True
|
||||
mes = obj.to_mesh(sce, True, 'RENDER')
|
||||
mes.transform(obj.matrix_world)
|
||||
dup = bpy.data.objects.new('dup', mes)
|
||||
sce.objects.link(dup)
|
||||
dup.instance_type = 'VERTS'
|
||||
sce.objects.active = dup
|
||||
bpy.ops.object.mode_set()
|
||||
ver = mes.vertices
|
||||
|
||||
for i in range(250):
|
||||
fin = True
|
||||
for i in dup.data.edges:
|
||||
d = ver[i.vertices[0]].co - ver[i.vertices[1]].co
|
||||
if d.length > self.size:
|
||||
ver[i.vertices[0]].select = True
|
||||
ver[i.vertices[1]].select = True
|
||||
fin = False
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.mesh.subdivide(number_cuts=1, smoothness=self.smooth)
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
bpy.ops.object.editmode_toggle()
|
||||
if fin:
|
||||
break
|
||||
|
||||
for i in ver:
|
||||
for n in range(3):
|
||||
i.co[n] -= (.001 + i.co[n]) % self.size
|
||||
|
||||
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.remove_doubles(threshold=0.0001)
|
||||
bpy.ops.mesh.delete(type='EDGE_FACE')
|
||||
bpy.ops.object.mode_set()
|
||||
sca = self.size * (100 - self.gap) * .005
|
||||
bpy.ops.mesh.primitive_cube_add(layers=[True] + [False] * 19)
|
||||
bpy.ops.transform.resize(value=[sca] * 3)
|
||||
bpy.context.view_layer.objects.active = dup
|
||||
bpy.ops.object.parent_set(type='OBJECT')
|
||||
|
||||
|
||||
class Pixelate(Operator):
|
||||
bl_idname = "object.pixelate"
|
||||
bl_label = "Pixelate Object"
|
||||
bl_description = ("Create a 3d pixelated version of the object\n"
|
||||
"using a Duplivert Box around each copied vertex\n"
|
||||
"With high poly objects, it can take some time\n"
|
||||
"Needs an existing Active Mesh Object")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
size: FloatProperty(
|
||||
name="Size",
|
||||
min=.05, max=5,
|
||||
default=.25,
|
||||
description="Size of the cube / grid \n"
|
||||
"Small values (below 0.1) can create a high polygon count"
|
||||
)
|
||||
gap: IntProperty(
|
||||
name="Gap",
|
||||
min=0, max=90,
|
||||
default=10,
|
||||
subtype='PERCENTAGE',
|
||||
description="Separation - percent of size"
|
||||
)
|
||||
smooth: FloatProperty(
|
||||
name="Smooth",
|
||||
min=0, max=1,
|
||||
default=.0,
|
||||
description="Smooth factor when subdividing mesh"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (context.active_object and
|
||||
context.active_object.type == 'MESH' and
|
||||
context.mode == 'OBJECT')
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.prop(self, "size")
|
||||
col.prop(self, "gap")
|
||||
layout.prop(self, "smooth")
|
||||
|
||||
def execute(self, context):
|
||||
objeto = bpy.context.object
|
||||
try:
|
||||
pix(self, objeto)
|
||||
|
||||
except Exception as e:
|
||||
self.report({'WARNING'},
|
||||
"Some operations could not be performed (See Console for more info)")
|
||||
|
||||
print("\n[Add Advanced Objects]\nOperator: "
|
||||
"object.pixelate\nError: {}".format(e))
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(Pixelate)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(Pixelate)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
register()
|
|
@ -1,201 +0,0 @@
|
|||
# gpl: author Dannyboy
|
||||
|
||||
bl_info = {
|
||||
"name": "Add Random Box Structure",
|
||||
"author": "Dannyboy",
|
||||
"version": (1, 0, 1),
|
||||
"location": "View3D > Add > Make Box Structure",
|
||||
"description": "Fill selected box shaped meshes with randomly sized cubes",
|
||||
"warning": "",
|
||||
"wiki_url": "",
|
||||
"tracker_url": "dannyboypython.blogspot.com",
|
||||
"category": "Object"}
|
||||
|
||||
import bpy
|
||||
import random
|
||||
from bpy.types import Operator
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
IntProperty,
|
||||
)
|
||||
|
||||
|
||||
class makestructure(Operator):
|
||||
bl_idname = "object.make_structure"
|
||||
bl_label = "Add Random Box Structure"
|
||||
bl_description = ("Create a randomized structure made of boxes\n"
|
||||
"with various control parameters\n"
|
||||
"Needs an existing Active Mesh Object")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
dc: BoolProperty(
|
||||
name="Delete Base Mesh(es)",
|
||||
default=True
|
||||
)
|
||||
wh: BoolProperty(
|
||||
name="Stay Within Bounds",
|
||||
description="Keeps cubes from exceeding base mesh bounds",
|
||||
default=True
|
||||
)
|
||||
uf: BoolProperty(
|
||||
name="Uniform Cube Quantity",
|
||||
default=False
|
||||
)
|
||||
qn: IntProperty(
|
||||
name="Cube Quantity",
|
||||
default=10,
|
||||
min=1, max=1500
|
||||
)
|
||||
mn: FloatVectorProperty(
|
||||
name="Min Scales",
|
||||
default=(0.1, 0.1, 0.1),
|
||||
subtype='XYZ'
|
||||
)
|
||||
mx: FloatVectorProperty(
|
||||
name="Max Scales",
|
||||
default=(2.0, 2.0, 2.0),
|
||||
subtype='XYZ'
|
||||
)
|
||||
lo: FloatVectorProperty(
|
||||
name="XYZ Offset",
|
||||
default=(0.0, 0.0, 0.0),
|
||||
subtype='XYZ'
|
||||
)
|
||||
rsd: FloatProperty(
|
||||
name="Random Seed",
|
||||
default=1
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return obj is not None and obj.type == "MESH" and obj.mode == "OBJECT"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
box = layout.box()
|
||||
box.label(text="Options:")
|
||||
box.prop(self, "dc")
|
||||
box.prop(self, "wh")
|
||||
box.prop(self, "uf")
|
||||
|
||||
box = layout.box()
|
||||
box.label(text="Parameters:")
|
||||
box.prop(self, "qn")
|
||||
box.prop(self, "mn")
|
||||
box.prop(self, "mx")
|
||||
box.prop(self, "lo")
|
||||
box.prop(self, "rsd")
|
||||
|
||||
def execute(self, context):
|
||||
rsdchange = self.rsd
|
||||
oblst = []
|
||||
uvyes = 0
|
||||
bpy.ops.collection.create(name='Cubagrouper')
|
||||
bpy.ops.collection.objects_remove()
|
||||
|
||||
for ob in bpy.context.selected_objects:
|
||||
oblst.append(ob)
|
||||
|
||||
for obj in oblst:
|
||||
bpy.ops.object.select_pattern(pattern=obj.name) # Select base mesh
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
if obj.data.uv_layers[:] != []:
|
||||
uvyes = 1
|
||||
else:
|
||||
uvyes = 0
|
||||
bpy.ops.object.collection_link(group='Cubagrouper')
|
||||
dim = obj.dimensions
|
||||
rot = obj.rotation_euler
|
||||
if self.uf is True:
|
||||
area = dim.x * dim.y * dim.z
|
||||
else:
|
||||
area = 75
|
||||
|
||||
for cube in range(round((area / 75) * self.qn)):
|
||||
random.seed(rsdchange)
|
||||
pmn = self.mn # Proxy values
|
||||
pmx = self.mx
|
||||
if self.wh is True:
|
||||
if dim.x < pmx.x: # Keeping things from exceeding proper size
|
||||
pmx.x = dim.x
|
||||
if dim.y < pmx.y:
|
||||
pmx.y = dim.y
|
||||
if dim.z < pmx.z:
|
||||
pmx.z = dim.z
|
||||
if 0.0 > pmn.x: # Keeping things from going under zero
|
||||
pmn.x = 0.0
|
||||
if 0.0 > pmn.y:
|
||||
pmn.y = 0.0
|
||||
if 0.0 > pmn.z:
|
||||
pmn.z = 0.0
|
||||
sx = (random.random() * (pmx.x - pmn.x)) + pmn.x # Just changed self.mx and .mn to pmx.
|
||||
sy = (random.random() * (pmx.y - pmn.y)) + pmn.y
|
||||
sz = (random.random() * (pmx.z - pmn.z)) + pmn.z
|
||||
if self.wh is True: # This keeps the cubes within the base mesh
|
||||
ex = (random.random() * (dim.x - sx)) - ((dim.x - sx) / 2) + obj.location.x
|
||||
wy = (random.random() * (dim.y - sy)) - ((dim.y - sy) / 2) + obj.location.y
|
||||
ze = (random.random() * (dim.z - sz)) - ((dim.z - sz) / 2) + obj.location.z
|
||||
elif self.wh is False:
|
||||
ex = (random.random() * dim.x) - (dim.x / 2) + obj.location.x
|
||||
wy = (random.random() * dim.y) - (dim.y / 2) + obj.location.y
|
||||
ze = (random.random() * dim.z) - (dim.z / 2) + obj.location.z
|
||||
bpy.ops.mesh.primitive_cube_add(
|
||||
radius=0.5, location=(ex + self.lo.x, wy + self.lo.y, ze + self.lo.z)
|
||||
)
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.transform.resize(
|
||||
value=(sx, sy, sz), constraint_axis=(True, True, True),
|
||||
orient_type='GLOBAL', mirror=False, proportional='DISABLED',
|
||||
proportional_edit_falloff='SMOOTH', proportional_size=1, release_confirm=True
|
||||
)
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
select = bpy.context.object # This is used to keep something selected for poll()
|
||||
bpy.ops.object.collection_link(group='Cubagrouper')
|
||||
rsdchange += 3
|
||||
bpy.ops.object.select_grouped(type='GROUP')
|
||||
bpy.ops.transform.rotate(
|
||||
value=rot[0], axis=(1, 0, 0), constraint_axis=(False, False, False),
|
||||
orient_type='GLOBAL', mirror=False, proportional='DISABLED',
|
||||
proportional_edit_falloff='SMOOTH', proportional_size=1, release_confirm=True
|
||||
)
|
||||
bpy.ops.transform.rotate(
|
||||
value=rot[1], axis=(0, 1, 0), constraint_axis=(False, False, False),
|
||||
orient_type='GLOBAL', mirror=False, proportional='DISABLED',
|
||||
proportional_edit_falloff='SMOOTH', proportional_size=1, release_confirm=True
|
||||
)
|
||||
bpy.ops.transform.rotate(
|
||||
value=rot[2], axis=(0, 0, 1), constraint_axis=(False, False, False),
|
||||
orient_type='GLOBAL', mirror=False, proportional='DISABLED',
|
||||
proportional_edit_falloff='SMOOTH', proportional_size=1, release_confirm=True
|
||||
)
|
||||
bpy.context.view_layer.objects.active = obj # Again needed to avoid poll() taking me down
|
||||
bpy.ops.object.make_links_data(type='MODIFIERS')
|
||||
bpy.ops.object.make_links_data(type='MATERIAL')
|
||||
|
||||
if uvyes == 1:
|
||||
bpy.ops.object.join_uvs()
|
||||
|
||||
bpy.ops.collection.objects_remove()
|
||||
bpy.context.view_layer.objects.active = select
|
||||
|
||||
if self.dc is True:
|
||||
bpy.context.collection.objects.unlink(obj)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(makestructure)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(makestructure)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,832 +0,0 @@
|
|||
# Copyright (c) 2012 Jorge Hernandez - Melendez
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# TODO : prop names into English, add missing tooltips
|
||||
|
||||
bl_info = {
|
||||
"name": "Rope Creator",
|
||||
"description": "Dynamic rope (with cloth) creator",
|
||||
"author": "Jorge Hernandez - Melenedez",
|
||||
"version": (0, 2, 2),
|
||||
"blender": (2, 73, 0),
|
||||
"location": "Left Toolbar > ClothRope",
|
||||
"warning": "",
|
||||
"wiki_url": "",
|
||||
"category": "Add Mesh"
|
||||
}
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
)
|
||||
|
||||
|
||||
def desocultar(quien):
|
||||
if quien == "todo":
|
||||
for ob in bpy.data.objects:
|
||||
ob.hide = False
|
||||
else:
|
||||
bpy.data.objects[quien].hide = False
|
||||
|
||||
|
||||
def deseleccionar_todo():
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
|
||||
|
||||
def seleccionar_todo():
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
|
||||
|
||||
def salir_de_editmode():
|
||||
if bpy.context.mode in ["EDIT", "EDIT_MESH", "EDIT_CURVE"]:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
|
||||
# Clear scene:
|
||||
def reset_scene():
|
||||
desocultar("todo")
|
||||
# playback to the start
|
||||
bpy.ops.screen.frame_jump(end=False)
|
||||
try:
|
||||
salir_de_editmode()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
area = bpy.context.area
|
||||
# expand everything in the outliner to be able to select children
|
||||
old_type = area.type
|
||||
area.type = 'OUTLINER'
|
||||
bpy.ops.outliner.expanded_toggle()
|
||||
|
||||
# restore the original context
|
||||
area.type = old_type
|
||||
|
||||
seleccionar_todo()
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
except Exception as e:
|
||||
print("\n[rope_alpha]\nfunction: reset_scene\nError: %s" % e)
|
||||
|
||||
|
||||
def entrar_en_editmode():
|
||||
if bpy.context.mode == "OBJECT":
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
|
||||
def select_all_in_edit_mode(ob):
|
||||
if ob.mode != 'EDIT':
|
||||
entrar_en_editmode()
|
||||
bpy.ops.mesh.select_all(action="DESELECT")
|
||||
bpy.context.tool_settings.mesh_select_mode = (True, False, False)
|
||||
salir_de_editmode()
|
||||
for v in ob.data.vertices:
|
||||
if not v.select:
|
||||
v.select = True
|
||||
entrar_en_editmode()
|
||||
|
||||
|
||||
def deselect_all_in_edit_mode(ob):
|
||||
if ob.mode != 'EDIT':
|
||||
entrar_en_editmode()
|
||||
bpy.ops.mesh.select_all(action="DESELECT")
|
||||
bpy.context.tool_settings.mesh_select_mode = (True, False, False)
|
||||
salir_de_editmode()
|
||||
for v in ob.data.vertices:
|
||||
if not v.select:
|
||||
v.select = False
|
||||
entrar_en_editmode()
|
||||
|
||||
|
||||
def which_vertex_are_selected(ob):
|
||||
for v in ob.data.vertices:
|
||||
if v.select:
|
||||
print(str(v.index))
|
||||
print("Vertex " + str(v.index) + " is selected")
|
||||
|
||||
|
||||
def seleccionar_por_nombre(nombre):
|
||||
scn = bpy.context.scene
|
||||
bpy.data.objects[nombre].select_set(True)
|
||||
|
||||
scn.objects.active = bpy.data.objects[nombre]
|
||||
|
||||
|
||||
def deseleccionar_por_nombre(nombre):
|
||||
bpy.data.objects[nombre].select_set(False)
|
||||
|
||||
|
||||
def crear_vertices(ob):
|
||||
ob.data.vertices.add(1)
|
||||
ob.data.update
|
||||
|
||||
|
||||
def borrar_elementos_seleccionados(tipo):
|
||||
if tipo == "vertices":
|
||||
bpy.ops.mesh.delete(type='VERT')
|
||||
|
||||
|
||||
def obtener_coords_vertex_seleccionados():
|
||||
coordenadas_de_vertices = []
|
||||
for ob in bpy.context.selected_objects:
|
||||
if ob.type == 'MESH':
|
||||
for v in ob.data.vertices:
|
||||
if v.select:
|
||||
coordenadas_de_vertices.append([v.co[0], v.co[1], v.co[2]])
|
||||
return coordenadas_de_vertices[0]
|
||||
|
||||
|
||||
def crear_locator(pos):
|
||||
bpy.ops.object.empty_add(
|
||||
type='PLAIN_AXES', radius=1, view_align=False,
|
||||
location=(pos[0], pos[1], pos[2]),
|
||||
layers=(True, False, False, False, False, False, False,
|
||||
False, False, False, False, False, False, False,
|
||||
False, False, False, False, False, False)
|
||||
)
|
||||
|
||||
|
||||
def extruir_vertices(longitud, cuantos_segmentos):
|
||||
bpy.ops.mesh.extrude_region_move(
|
||||
MESH_OT_extrude_region={"mirror": False},
|
||||
TRANSFORM_OT_translate={
|
||||
"value": (longitud / cuantos_segmentos, 0, 0),
|
||||
"constraint_axis": (True, False, False),
|
||||
"orient_type": 'GLOBAL', "mirror": False,
|
||||
"proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH',
|
||||
"proportional_size": 1, "snap": False, "snap_target": 'CLOSEST',
|
||||
"snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0),
|
||||
"gpencil_strokes": False, "texture_space": False,
|
||||
"remove_on_cancel": False, "release_confirm": False
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def select_all_vertex_in_curve_bezier(bc):
|
||||
for i in range(len(bc.data.splines[0].points)):
|
||||
bc.data.splines[0].points[i].select = True
|
||||
|
||||
|
||||
def deselect_all_vertex_in_curve_bezier(bc):
|
||||
for i in range(len(bc.data.splines[0].points)):
|
||||
bc.data.splines[0].points[i].select = False
|
||||
|
||||
|
||||
def ocultar_relationships():
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
area.spaces[0].show_relationship_lines = False
|
||||
|
||||
|
||||
class ClothRope(Operator):
|
||||
bl_idname = "clot.rope"
|
||||
bl_label = "Rope Cloth"
|
||||
bl_description = ("Create a new Scene with a Cloth modifier\n"
|
||||
"Rope Simulation with hooked Helper Objects")
|
||||
|
||||
ropelength: IntProperty(
|
||||
name="Rope Length",
|
||||
description="Length of the generated Rope",
|
||||
default=5
|
||||
)
|
||||
ropesegments: IntProperty(
|
||||
name="Rope Segments",
|
||||
description="Number of the Rope Segments",
|
||||
default=5
|
||||
)
|
||||
qcr: IntProperty(
|
||||
name="Collision Quality",
|
||||
description="Rope's Cloth modifier collsion quality",
|
||||
min=1, max=20,
|
||||
default=20
|
||||
)
|
||||
substeps: IntProperty(
|
||||
name="Rope Substeps",
|
||||
description="Rope's Cloth modifier quality",
|
||||
min=4, max=80,
|
||||
default=50
|
||||
)
|
||||
resrope: IntProperty(
|
||||
name="Rope Resolution",
|
||||
description="Rope's Bevel resolution",
|
||||
default=5
|
||||
)
|
||||
radiusrope: FloatProperty(
|
||||
name="Radius",
|
||||
description="Rope's Radius",
|
||||
min=0.04, max=1,
|
||||
default=0.04
|
||||
)
|
||||
hide_emptys: BoolProperty(
|
||||
name="Hide Empties",
|
||||
description="Hide Helper Objects",
|
||||
default=False
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
# add a new scene
|
||||
bpy.ops.scene.new(type="NEW")
|
||||
scene = bpy.context.scene
|
||||
scene.name = "Test Rope"
|
||||
seleccionar_todo()
|
||||
longitud = self.ropelength
|
||||
|
||||
# For the middle to have x segments between the first and
|
||||
# last point, must add 1 to the quantity:
|
||||
cuantos_segmentos = self.ropesegments + 1
|
||||
calidad_de_colision = self.qcr
|
||||
substeps = self.substeps
|
||||
deseleccionar_todo()
|
||||
# collect the possible empties that already exist in the data
|
||||
empties_prev = [obj.name for obj in bpy.data.objects if obj.type == "EMPTY"]
|
||||
|
||||
# create an empty that will be the parent of everything
|
||||
bpy.ops.object.empty_add(
|
||||
type='SPHERE', radius=1, view_align=False, location=(0, 0, 0),
|
||||
layers=(True, False, False, False, False, False, False, False,
|
||||
False, False, False, False, False, False, False, False,
|
||||
False, False, False, False)
|
||||
)
|
||||
ob = bpy.context.selected_objects[0]
|
||||
ob.name = "Rope"
|
||||
# .001 and friends
|
||||
rope_name = ob.name
|
||||
deseleccionar_todo()
|
||||
|
||||
# create a plane and delete it
|
||||
bpy.ops.mesh.primitive_plane_add(
|
||||
radius=1, view_align=False, enter_editmode=False, location=(0, 0, 0),
|
||||
layers=(True, False, False, False, False, False, False, False, False,
|
||||
False, False, False, False, False, False, False, False,
|
||||
False, False, False)
|
||||
)
|
||||
ob = bpy.context.selected_objects[0]
|
||||
# rename:
|
||||
ob.name = "cuerda"
|
||||
# .001 and friends
|
||||
cuerda_1_name = ob.name
|
||||
|
||||
entrar_en_editmode() # enter edit mode
|
||||
select_all_in_edit_mode(ob)
|
||||
|
||||
borrar_elementos_seleccionados("vertices")
|
||||
salir_de_editmode() # leave edit mode
|
||||
crear_vertices(ob) # create a vertex
|
||||
|
||||
# Creating a Group for the PIN
|
||||
# Group contains the vertices of the pin and the Group.001 contains the single main line
|
||||
entrar_en_editmode() # enter edit mode
|
||||
bpy.ops.object.vertex_group_add() # create a group
|
||||
select_all_in_edit_mode(ob)
|
||||
bpy.ops.object.vertex_group_assign() # assign it
|
||||
|
||||
salir_de_editmode() # leave edit mode
|
||||
ob.vertex_groups[0].name = "Pin"
|
||||
deseleccionar_todo()
|
||||
seleccionar_por_nombre(cuerda_1_name)
|
||||
|
||||
# extrude vertices:
|
||||
for i in range(cuantos_segmentos):
|
||||
entrar_en_editmode()
|
||||
extruir_vertices(longitud, cuantos_segmentos)
|
||||
# delete the PIN group
|
||||
bpy.ops.object.vertex_group_remove_from()
|
||||
# get the direction to create the locator on it's position
|
||||
pos = obtener_coords_vertex_seleccionados()
|
||||
|
||||
salir_de_editmode() # leave edit mode
|
||||
# create locator at position
|
||||
crear_locator(pos)
|
||||
deseleccionar_todo()
|
||||
seleccionar_por_nombre(cuerda_1_name)
|
||||
deseleccionar_todo()
|
||||
|
||||
seleccionar_por_nombre(cuerda_1_name) # select the rope
|
||||
entrar_en_editmode()
|
||||
|
||||
pos = obtener_coords_vertex_seleccionados() # get their positions
|
||||
salir_de_editmode()
|
||||
# create the last locator
|
||||
crear_locator(pos)
|
||||
deseleccionar_todo()
|
||||
seleccionar_por_nombre(cuerda_1_name)
|
||||
entrar_en_editmode() # enter edit mode
|
||||
bpy.ops.object.vertex_group_add() # Creating Master guide group
|
||||
select_all_in_edit_mode(ob)
|
||||
bpy.ops.object.vertex_group_assign() # and assign it
|
||||
ob.vertex_groups[1].name = "Guide_rope"
|
||||
|
||||
# extrude the Curve so it has a minimum thickness for collide
|
||||
bpy.ops.mesh.extrude_region_move(
|
||||
MESH_OT_extrude_region={"mirror": False},
|
||||
TRANSFORM_OT_translate={
|
||||
"value": (0, 0.005, 0), "constraint_axis": (False, True, False),
|
||||
"orient_type": 'GLOBAL', "mirror": False,
|
||||
"proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH',
|
||||
"proportional_size": 1, "snap": False, "snap_target": 'CLOSEST',
|
||||
"snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0),
|
||||
"gpencil_strokes": False, "texture_space": False,
|
||||
"remove_on_cancel": False, "release_confirm": False
|
||||
}
|
||||
)
|
||||
bpy.ops.object.vertex_group_remove_from()
|
||||
deselect_all_in_edit_mode(ob)
|
||||
salir_de_editmode()
|
||||
bpy.ops.object.modifier_add(type='CLOTH')
|
||||
bpy.context.object.modifiers["Cloth"].settings.use_pin_cloth = True
|
||||
bpy.context.object.modifiers["Cloth"].settings.vertex_group_mass = "Pin"
|
||||
bpy.context.object.modifiers["Cloth"].collision_settings.collision_quality = calidad_de_colision
|
||||
bpy.context.object.modifiers["Cloth"].settings.quality = substeps
|
||||
|
||||
# Duplicate to convert into Curve:
|
||||
# select the vertices that are the part of the Group.001
|
||||
seleccionar_por_nombre(cuerda_1_name)
|
||||
entrar_en_editmode()
|
||||
bpy.ops.mesh.select_all(action="DESELECT")
|
||||
bpy.context.tool_settings.mesh_select_mode = (True, False, False)
|
||||
salir_de_editmode()
|
||||
gi = ob.vertex_groups["Guide_rope"].index # get group index
|
||||
|
||||
for v in ob.data.vertices:
|
||||
for g in v.groups:
|
||||
if g.group == gi: # compare with index in VertexGroupElement
|
||||
v.select = True
|
||||
|
||||
# now we have to make a table of names of cuerdas to see which one will be new
|
||||
cuerda_names = [obj.name for obj in bpy.data.objects if "cuerda" in obj.name]
|
||||
|
||||
entrar_en_editmode()
|
||||
|
||||
# we already have the selected guide:
|
||||
# duplicate it:
|
||||
bpy.ops.mesh.duplicate_move(
|
||||
MESH_OT_duplicate={"mode": 1},
|
||||
TRANSFORM_OT_translate={
|
||||
"value": (0, 0, 0), "constraint_axis": (False, False, False),
|
||||
"orient_type": 'GLOBAL', "mirror": False,
|
||||
"proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH',
|
||||
"proportional_size": 1, "snap": False, "snap_target": 'CLOSEST',
|
||||
"snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0),
|
||||
"gpencil_strokes": False, "texture_space": False,
|
||||
"remove_on_cancel": False, "release_confirm": False
|
||||
}
|
||||
)
|
||||
# separate the selections:
|
||||
bpy.ops.mesh.separate(type='SELECTED')
|
||||
salir_de_editmode()
|
||||
deseleccionar_todo()
|
||||
|
||||
cuerda_2_name = "cuerda.001"
|
||||
test = []
|
||||
for obj in bpy.data.objects:
|
||||
if "cuerda" in obj.name and obj.name not in cuerda_names:
|
||||
cuerda_2_name = obj.name
|
||||
test.append(obj.name)
|
||||
|
||||
seleccionar_por_nombre(cuerda_2_name)
|
||||
|
||||
# from the newly created curve remove the Cloth:
|
||||
bpy.ops.object.modifier_remove(modifier="Cloth")
|
||||
# convert the Curve:
|
||||
bpy.ops.object.convert(target='CURVE')
|
||||
|
||||
# all Empties that are not previously present
|
||||
emptys = []
|
||||
for eo in bpy.data.objects:
|
||||
if eo.type == 'EMPTY' and eo.name not in empties_prev:
|
||||
if eo.name != rope_name:
|
||||
emptys.append(eo)
|
||||
|
||||
# select and deselect:
|
||||
bc = bpy.data.objects[cuerda_2_name]
|
||||
n = 0
|
||||
|
||||
for e in emptys:
|
||||
deseleccionar_todo()
|
||||
seleccionar_por_nombre(e.name)
|
||||
seleccionar_por_nombre(bc.name)
|
||||
entrar_en_editmode()
|
||||
deselect_all_vertex_in_curve_bezier(bc)
|
||||
bc.data.splines[0].points[n].select = True
|
||||
bpy.ops.object.hook_add_selob(use_bone=False)
|
||||
salir_de_editmode()
|
||||
n = n + 1
|
||||
|
||||
ob = bpy.data.objects[cuerda_1_name]
|
||||
n = 0
|
||||
|
||||
for e in emptys:
|
||||
deseleccionar_todo()
|
||||
seleccionar_por_nombre(e.name)
|
||||
seleccionar_por_nombre(ob.name)
|
||||
entrar_en_editmode()
|
||||
bpy.ops.mesh.select_all(action="DESELECT")
|
||||
bpy.context.tool_settings.mesh_select_mode = (True, False, False)
|
||||
salir_de_editmode()
|
||||
|
||||
for v in ob.data.vertices:
|
||||
if v.select:
|
||||
v.select = False
|
||||
ob.data.vertices[n].select = True
|
||||
entrar_en_editmode()
|
||||
bpy.ops.object.vertex_parent_set()
|
||||
|
||||
salir_de_editmode()
|
||||
n = n + 1
|
||||
|
||||
# hide the Empties:
|
||||
deseleccionar_todo()
|
||||
|
||||
# all parented to the spherical empty:
|
||||
seleccionar_por_nombre(cuerda_2_name)
|
||||
seleccionar_por_nombre(cuerda_1_name)
|
||||
seleccionar_por_nombre(rope_name)
|
||||
bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)
|
||||
deseleccionar_todo()
|
||||
|
||||
# do not display the relations
|
||||
ocultar_relationships()
|
||||
seleccionar_por_nombre(cuerda_2_name)
|
||||
|
||||
# curved rope settings:
|
||||
bpy.context.object.data.fill_mode = 'FULL'
|
||||
bpy.context.object.data.bevel_depth = self.radiusrope
|
||||
bpy.context.object.data.bevel_resolution = self.resrope
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self, width=350)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
col = box.column(align=True)
|
||||
|
||||
col.label(text="Rope settings:")
|
||||
rowsub0 = col.row()
|
||||
rowsub0.prop(self, "ropelength", text="Length")
|
||||
rowsub0.prop(self, "ropesegments", text="Segments")
|
||||
rowsub0.prop(self, "radiusrope", text="Radius")
|
||||
|
||||
col.label(text="Quality Settings:")
|
||||
col.prop(self, "resrope", text="Resolution curve")
|
||||
col.prop(self, "qcr", text="Quality Collision")
|
||||
col.prop(self, "substeps", text="Substeps")
|
||||
|
||||
|
||||
class BallRope(Operator):
|
||||
bl_idname = "ball.rope"
|
||||
bl_label = "Wrecking Ball"
|
||||
bl_description = ("Create a new Scene with a Rigid Body simulation of\n"
|
||||
"Wrecking Ball on a rope")
|
||||
|
||||
# defaults rope ball
|
||||
ropelength2: IntProperty(
|
||||
name="Rope Length",
|
||||
description="Length of the Wrecking Ball rope",
|
||||
default=10
|
||||
)
|
||||
ropesegments2: IntProperty(
|
||||
name="Rope Segments",
|
||||
description="Number of the Wrecking Ball rope segments",
|
||||
min=0, max=999,
|
||||
default=6
|
||||
)
|
||||
radiuscubes: FloatProperty(
|
||||
name="Cube Radius",
|
||||
description="Size of the Linked Cubes helpers",
|
||||
default=0.5
|
||||
)
|
||||
radiusrope: FloatProperty(
|
||||
name="Rope Radius",
|
||||
description="Radius of the Rope",
|
||||
default=0.4
|
||||
)
|
||||
worldsteps: IntProperty(
|
||||
name="World Steps",
|
||||
description="Rigid Body Solver world steps per second (update)",
|
||||
min=60, max=1000,
|
||||
default=250
|
||||
)
|
||||
solveriterations: IntProperty(
|
||||
name="Solver Iterations",
|
||||
description="How many times the Rigid Body Solver should run",
|
||||
min=10, max=100,
|
||||
default=50
|
||||
)
|
||||
massball: IntProperty(
|
||||
name="Ball Mass",
|
||||
description="Mass of the Wrecking Ball",
|
||||
default=1
|
||||
)
|
||||
resrope: IntProperty(
|
||||
name="Resolution",
|
||||
description="Rope resolution",
|
||||
default=4
|
||||
)
|
||||
grados: FloatProperty(
|
||||
name="Degrees",
|
||||
description="Angle of the Wrecking Ball compared to the Ground Plane",
|
||||
default=45
|
||||
)
|
||||
separacion: FloatProperty(
|
||||
name="Link Cubes Gap",
|
||||
description="Space between the Rope's Linked Cubes",
|
||||
default=0.1
|
||||
)
|
||||
hidecubes: BoolProperty(
|
||||
name="Hide Link Cubes",
|
||||
description="Hide helper geometry for the Rope",
|
||||
default=False
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
world_steps = self.worldsteps
|
||||
solver_iterations = self.solveriterations
|
||||
longitud = self.ropelength2
|
||||
|
||||
# make a + 2, so the segments will be between the two end points...
|
||||
segmentos = self.ropesegments2 + 2
|
||||
offset_del_suelo = 1
|
||||
offset_del_suelo_real = (longitud / 2) + (segmentos / 2)
|
||||
radio = self.radiuscubes
|
||||
radiorope = self.radiusrope
|
||||
masa = self.massball
|
||||
resolucion = self.resrope
|
||||
rotrope = self.grados
|
||||
separation = self.separacion
|
||||
hidecubeslinks = self.hidecubes
|
||||
|
||||
# add new scene
|
||||
bpy.ops.scene.new(type="NEW")
|
||||
scene = bpy.context.scene
|
||||
scene.name = "Test Ball"
|
||||
|
||||
# collect the possible constraint empties that already exist in the data
|
||||
constraint_prev = [obj.name for obj in bpy.data.objects if
|
||||
obj.type == "EMPTY" and "Constraint" in obj.name]
|
||||
# floor:
|
||||
bpy.ops.mesh.primitive_cube_add(
|
||||
radius=1, view_align=False, enter_editmode=False, location=(0, 0, 0),
|
||||
layers=(True, False, False, False, False, False, False, False, False,
|
||||
False, False, False, False, False, False, False, False,
|
||||
False, False, False)
|
||||
)
|
||||
bpy.context.object.scale.x = 10 + longitud
|
||||
bpy.context.object.scale.y = 10 + longitud
|
||||
bpy.context.object.scale.z = 0.05
|
||||
bpy.context.object.name = "groundplane"
|
||||
# The secret agents .001, 002 etc.
|
||||
groundplane_name = bpy.context.object.name
|
||||
|
||||
bpy.ops.rigidbody.objects_add(type='PASSIVE')
|
||||
|
||||
# create the first cube:
|
||||
cuboslink = []
|
||||
n = 0
|
||||
for i in range(segmentos):
|
||||
# if 0 start from 1
|
||||
if i == 0:
|
||||
i = offset_del_suelo
|
||||
else: # if it is not 0, add one so it doesn't step on the first one starting from 1
|
||||
i = i + offset_del_suelo
|
||||
separacion = longitud * 2 / segmentos # distance between linked cubes
|
||||
bpy.ops.mesh.primitive_cube_add(
|
||||
radius=1, view_align=False, enter_editmode=False,
|
||||
location=(0, 0, i * separacion),
|
||||
layers=(True, False, False, False, False, False, False, False,
|
||||
False, False, False, False, False, False, False, False,
|
||||
False, False, False, False)
|
||||
)
|
||||
bpy.ops.rigidbody.objects_add(type='ACTIVE')
|
||||
bpy.context.object.name = "CubeLink"
|
||||
if n != 0:
|
||||
bpy.context.object.display_type = 'WIRE'
|
||||
bpy.context.object.hide_render = True
|
||||
n += 1
|
||||
bpy.context.object.scale.z = (longitud * 2) / (segmentos * 2) - separation
|
||||
bpy.context.object.scale.x = radio
|
||||
bpy.context.object.scale.y = radio
|
||||
cuboslink.append(bpy.context.object)
|
||||
|
||||
for i in range(len(cuboslink)):
|
||||
deseleccionar_todo()
|
||||
if i != len(cuboslink) - 1:
|
||||
nombre1 = cuboslink[i]
|
||||
nombre2 = cuboslink[i + 1]
|
||||
seleccionar_por_nombre(nombre1.name)
|
||||
seleccionar_por_nombre(nombre2.name)
|
||||
bpy.ops.rigidbody.connect()
|
||||
|
||||
# select by name
|
||||
constraint_new = [
|
||||
obj.name for obj in bpy.data.objects if
|
||||
obj.type == "EMPTY" and "Constraint" in obj.name and
|
||||
obj.name not in constraint_prev
|
||||
]
|
||||
|
||||
for names in constraint_new:
|
||||
seleccionar_por_nombre(names)
|
||||
|
||||
for c in bpy.context.selected_objects:
|
||||
c.rigid_body_constraint.type = 'POINT'
|
||||
deseleccionar_todo()
|
||||
|
||||
# create a Bezier curve:
|
||||
bpy.ops.curve.primitive_bezier_curve_add(
|
||||
radius=1, view_align=False, enter_editmode=False, location=(0, 0, 0),
|
||||
layers=(True, False, False, False, False, False, False, False, False,
|
||||
False, False, False, False, False, False, False, False, False, False, False)
|
||||
)
|
||||
bpy.context.object.name = "Cuerda"
|
||||
# Blender will automatically append the .001
|
||||
# if it is already in data
|
||||
real_name = bpy.context.object.name
|
||||
|
||||
for i in range(len(cuboslink)):
|
||||
cubonombre = cuboslink[i].name
|
||||
seleccionar_por_nombre(cubonombre)
|
||||
seleccionar_por_nombre(real_name)
|
||||
x = cuboslink[i].location[0]
|
||||
y = cuboslink[i].location[1]
|
||||
z = cuboslink[i].location[2]
|
||||
|
||||
# if it is 0 make it start from 1 as the offset from the ground...
|
||||
if i == 0:
|
||||
i = offset_del_suelo
|
||||
else: # if it is not 0, add one so it doesn't step on the first one starting from 1
|
||||
i = i + offset_del_suelo
|
||||
|
||||
salir_de_editmode()
|
||||
entrar_en_editmode()
|
||||
|
||||
if i == 1:
|
||||
# select all the vertices and delete them
|
||||
select_all_vertex_in_curve_bezier(bpy.data.objects[real_name])
|
||||
bpy.ops.curve.delete(type='VERT')
|
||||
# create the first vertex:
|
||||
bpy.ops.curve.vertex_add(location=(x, y, z))
|
||||
else:
|
||||
# extrude the rest:
|
||||
bpy.ops.curve.extrude_move(
|
||||
CURVE_OT_extrude={"mode": 'TRANSLATION'},
|
||||
TRANSFORM_OT_translate={
|
||||
"value": (0, 0, z / i),
|
||||
"constraint_axis": (False, False, True),
|
||||
"orient_type": 'GLOBAL', "mirror": False,
|
||||
"proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH',
|
||||
"proportional_size": 1, "snap": False, "snap_target": 'CLOSEST',
|
||||
"snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0),
|
||||
"gpencil_strokes": False, "texture_space": False,
|
||||
"remove_on_cancel": False, "release_confirm": False
|
||||
}
|
||||
)
|
||||
bpy.ops.object.hook_add_selob(use_bone=False)
|
||||
salir_de_editmode()
|
||||
bpy.context.object.data.bevel_resolution = resolucion
|
||||
deseleccionar_todo()
|
||||
|
||||
# create a sphere ball:
|
||||
deseleccionar_todo()
|
||||
seleccionar_por_nombre(cuboslink[0].name)
|
||||
entrar_en_editmode()
|
||||
z = cuboslink[0].scale.z + longitud / 2
|
||||
bpy.ops.view3d.snap_cursor_to_selected()
|
||||
bpy.ops.mesh.primitive_uv_sphere_add(
|
||||
view_align=False, enter_editmode=False,
|
||||
layers=(True, False, False, False, False, False, False,
|
||||
False, False, False, False, False, False, False,
|
||||
False, False, False, False, False, False)
|
||||
)
|
||||
bpy.ops.transform.translate(
|
||||
value=(0, 0, -z + 2), constraint_axis=(False, False, True),
|
||||
orient_type='GLOBAL', mirror=False, proportional='DISABLED',
|
||||
proportional_edit_falloff='SMOOTH', proportional_size=1
|
||||
)
|
||||
bpy.ops.transform.resize(
|
||||
value=(longitud / 2, longitud / 2, longitud / 2),
|
||||
constraint_axis=(False, False, False),
|
||||
orient_type='GLOBAL',
|
||||
mirror=False, proportional='DISABLED',
|
||||
proportional_edit_falloff='SMOOTH', proportional_size=1
|
||||
)
|
||||
deselect_all_in_edit_mode(cuboslink[0])
|
||||
salir_de_editmode()
|
||||
bpy.ops.object.shade_smooth()
|
||||
bpy.context.object.rigid_body.mass = masa
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS')
|
||||
|
||||
# move it all up a bit more:
|
||||
seleccionar_todo()
|
||||
deseleccionar_por_nombre(groundplane_name)
|
||||
bpy.ops.transform.translate(
|
||||
value=(0, 0, offset_del_suelo_real),
|
||||
constraint_axis=(False, False, True),
|
||||
orient_type='GLOBAL', mirror=False,
|
||||
proportional='DISABLED', proportional_edit_falloff='SMOOTH',
|
||||
proportional_size=1
|
||||
)
|
||||
|
||||
deseleccionar_todo()
|
||||
seleccionar_por_nombre(cuboslink[-1].name)
|
||||
bpy.ops.rigidbody.objects_add(type='PASSIVE')
|
||||
|
||||
bpy.context.scene.rigidbody_world.steps_per_second = world_steps
|
||||
bpy.context.scene.rigidbody_world.solver_iterations = solver_iterations
|
||||
|
||||
# move everything from the top one:
|
||||
seleccionar_por_nombre(cuboslink[-1].name)
|
||||
bpy.ops.view3d.snap_cursor_to_selected()
|
||||
seleccionar_todo()
|
||||
deseleccionar_por_nombre(groundplane_name)
|
||||
deseleccionar_por_nombre(cuboslink[-1].name)
|
||||
bpy.context.space_data.pivot_point = 'CURSOR'
|
||||
bpy.ops.transform.rotate(
|
||||
value=rotrope, axis=(1, 0, 0),
|
||||
constraint_axis=(True, False, False),
|
||||
orient_type='GLOBAL',
|
||||
mirror=False, proportional='DISABLED',
|
||||
proportional_edit_falloff='SMOOTH',
|
||||
proportional_size=1
|
||||
)
|
||||
bpy.context.space_data.pivot_point = 'MEDIAN_POINT'
|
||||
deseleccionar_todo()
|
||||
|
||||
seleccionar_por_nombre(real_name)
|
||||
bpy.context.object.data.fill_mode = 'FULL'
|
||||
bpy.context.object.data.bevel_depth = radiorope
|
||||
for ob in bpy.data.objects:
|
||||
if ob.name != cuboslink[0].name:
|
||||
if ob.name.find("CubeLink") >= 0:
|
||||
deseleccionar_todo()
|
||||
seleccionar_por_nombre(ob.name)
|
||||
if hidecubeslinks:
|
||||
bpy.context.object.hide = True
|
||||
ocultar_relationships()
|
||||
deseleccionar_todo()
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self, width=350)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
col = box.column(align=True)
|
||||
|
||||
col.label(text="Rope settings:")
|
||||
rowsub0 = col.row()
|
||||
rowsub0.prop(self, "hidecubes", text="Hide Link Cubes")
|
||||
|
||||
rowsub1 = col.row(align=True)
|
||||
rowsub1.prop(self, "ropelength2", text="Length")
|
||||
rowsub1.prop(self, "ropesegments2", text="Segments")
|
||||
|
||||
rowsub2 = col.row(align=True)
|
||||
rowsub2.prop(self, "radiuscubes", text="Radius Link Cubes")
|
||||
rowsub2.prop(self, "radiusrope", text="Radius Rope")
|
||||
|
||||
rowsub3 = col.row(align=True)
|
||||
rowsub3.prop(self, "grados", text="Degrees")
|
||||
rowsub3.prop(self, "separacion", text="Separation Link Cubes")
|
||||
|
||||
col.label(text="Quality Settings:")
|
||||
col.prop(self, "resrope", text="Resolution Rope")
|
||||
col.prop(self, "massball", text="Ball Mass")
|
||||
col.prop(self, "worldsteps", text="World Steps")
|
||||
col.prop(self, "solveriterations", text="Solver Iterarions")
|
||||
|
||||
|
||||
# Register
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,195 +0,0 @@
|
|||
# gpl: author meta-androcto
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
|
||||
|
||||
class add_BI_scene(Operator):
|
||||
bl_idname = "bi.add_scene"
|
||||
bl_label = "Create test scene"
|
||||
bl_description = "Blender Internal renderer Scene with Objects"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
blend_data = context.blend_data
|
||||
# ob = bpy.context.active_object
|
||||
|
||||
# add new scene
|
||||
bpy.ops.scene.new(type="NEW")
|
||||
scene = bpy.context.scene
|
||||
scene.name = "scene_materials"
|
||||
|
||||
# render settings
|
||||
render = scene.render
|
||||
render.resolution_x = 1920
|
||||
render.resolution_y = 1080
|
||||
render.resolution_percentage = 50
|
||||
|
||||
# add new world
|
||||
world = bpy.data.worlds.new("Materials_World")
|
||||
scene.world = world
|
||||
world.use_sky_blend = True
|
||||
world.use_sky_paper = True
|
||||
world.horizon_color = (0.004393, 0.02121, 0.050)
|
||||
world.zenith_color = (0.03335, 0.227, 0.359)
|
||||
world.light_settings.use_ambient_occlusion = True
|
||||
world.light_settings.ao_factor = 0.25
|
||||
|
||||
# add camera
|
||||
bpy.ops.object.camera_add(
|
||||
location=(7.48113, -6.50764, 5.34367),
|
||||
rotation=(1.109319, 0.010817, 0.814928)
|
||||
)
|
||||
cam = bpy.context.active_object.data
|
||||
cam.lens = 35
|
||||
cam.display_size = 0.1
|
||||
bpy.ops.view3d.viewnumpad(type='CAMERA')
|
||||
|
||||
# add point lamp
|
||||
bpy.ops.object.light_add(
|
||||
type="POINT", location=(4.07625, 1.00545, 5.90386),
|
||||
rotation=(0.650328, 0.055217, 1.866391)
|
||||
)
|
||||
lamp1 = bpy.context.active_object.data
|
||||
lamp1.name = "Point_Right"
|
||||
lamp1.energy = 1.0
|
||||
lamp1.distance = 30.0
|
||||
lamp1.shadow_method = "RAY_SHADOW"
|
||||
lamp1.use_sphere = True
|
||||
|
||||
# add point lamp2
|
||||
bpy.ops.object.light_add(
|
||||
type="POINT", location=(-0.57101, -4.24586, 5.53674),
|
||||
rotation=(1.571, 0, 0.785)
|
||||
)
|
||||
lamp2 = bpy.context.active_object.data
|
||||
lamp2.name = "Point_Left"
|
||||
lamp2.energy = 1.0
|
||||
lamp2.distance = 30.0
|
||||
|
||||
# Add cube
|
||||
bpy.ops.mesh.primitive_cube_add()
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.mesh.subdivide(number_cuts=2)
|
||||
bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
cube = bpy.context.active_object
|
||||
# add new material
|
||||
cubeMaterial = blend_data.materials.new("Cube_Material")
|
||||
bpy.ops.object.material_slot_add()
|
||||
cube.material_slots[0].material = cubeMaterial
|
||||
# Diffuse
|
||||
cubeMaterial.preview_render_type = "CUBE"
|
||||
cubeMaterial.diffuse_color = (1.000, 0.373, 0.00)
|
||||
cubeMaterial.diffuse_shader = 'OREN_NAYAR'
|
||||
cubeMaterial.diffuse_intensity = 1.0
|
||||
cubeMaterial.roughness = 0.09002
|
||||
# Specular
|
||||
cubeMaterial.specular_color = (1.000, 0.800, 0.136)
|
||||
cubeMaterial.specular_shader = "PHONG"
|
||||
cubeMaterial.specular_intensity = 1.0
|
||||
cubeMaterial.specular_hardness = 511.0
|
||||
# Shading
|
||||
cubeMaterial.ambient = 1.00
|
||||
cubeMaterial.use_cubic = False
|
||||
# Transparency
|
||||
cubeMaterial.use_transparency = False
|
||||
cubeMaterial.alpha = 0
|
||||
# Mirror
|
||||
cubeMaterial.raytrace_mirror.use = True
|
||||
cubeMaterial.mirror_color = (1.000, 0.793, 0.0)
|
||||
cubeMaterial.raytrace_mirror.reflect_factor = 0.394
|
||||
cubeMaterial.raytrace_mirror.fresnel = 2.0
|
||||
cubeMaterial.raytrace_mirror.fresnel_factor = 1.641
|
||||
cubeMaterial.raytrace_mirror.fade_to = "FADE_TO_SKY"
|
||||
cubeMaterial.raytrace_mirror.gloss_anisotropic = 1.0
|
||||
# Shadow
|
||||
cubeMaterial.use_transparent_shadows = True
|
||||
|
||||
# Add a texture
|
||||
cubetex = blend_data.textures.new("CloudTex", type='CLOUDS')
|
||||
cubetex.noise_type = 'SOFT_NOISE'
|
||||
cubetex.noise_scale = 0.25
|
||||
mtex = cubeMaterial.texture_slots.add()
|
||||
mtex.texture = cubetex
|
||||
mtex.texture_coords = 'ORCO'
|
||||
mtex.scale = (0.800, 0.800, 0.800)
|
||||
mtex.use_map_mirror = True
|
||||
mtex.mirror_factor = 0.156
|
||||
mtex.use_map_color_diffuse = True
|
||||
mtex.diffuse_color_factor = 0.156
|
||||
mtex.use_map_normal = True
|
||||
mtex.normal_factor = 0.010
|
||||
mtex.blend_type = "ADD"
|
||||
mtex.use_rgb_to_intensity = True
|
||||
mtex.color = (1.000, 0.207, 0.000)
|
||||
|
||||
# Add monkey
|
||||
bpy.ops.mesh.primitive_monkey_add(location=(-0.1, 0.08901, 1.505))
|
||||
bpy.ops.transform.rotate(value=(1.15019), axis=(0, 0, 1))
|
||||
bpy.ops.transform.rotate(value=(-0.673882), axis=(0, 1, 0))
|
||||
bpy.ops.transform.rotate(value=-0.055, axis=(1, 0, 0))
|
||||
bpy.ops.object.modifier_add(type='SUBSURF')
|
||||
bpy.ops.object.shade_smooth()
|
||||
monkey = bpy.context.active_object
|
||||
# add new material
|
||||
monkeyMaterial = blend_data.materials.new("Monkey_Material")
|
||||
bpy.ops.object.material_slot_add()
|
||||
monkey.material_slots[0].material = monkeyMaterial
|
||||
# Material settings
|
||||
monkeyMaterial.preview_render_type = "MONKEY"
|
||||
monkeyMaterial.diffuse_color = (0.239, 0.288, 0.288)
|
||||
monkeyMaterial.specular_color = (0.604, 0.465, 0.136)
|
||||
monkeyMaterial.diffuse_shader = 'LAMBERT'
|
||||
monkeyMaterial.diffuse_intensity = 1.0
|
||||
monkeyMaterial.specular_intensity = 0.3
|
||||
monkeyMaterial.ambient = 0
|
||||
monkeyMaterial.type = 'SURFACE'
|
||||
monkeyMaterial.use_cubic = True
|
||||
monkeyMaterial.use_transparency = False
|
||||
monkeyMaterial.alpha = 0
|
||||
monkeyMaterial.use_transparent_shadows = True
|
||||
monkeyMaterial.raytrace_mirror.use = True
|
||||
monkeyMaterial.raytrace_mirror.reflect_factor = 0.65
|
||||
monkeyMaterial.raytrace_mirror.fade_to = "FADE_TO_MATERIAL"
|
||||
|
||||
# Add plane
|
||||
bpy.ops.mesh.primitive_plane_add(
|
||||
radius=50, view_align=False, enter_editmode=False, location=(0, 0, -1)
|
||||
)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.transform.rotate(
|
||||
value=-0.8, axis=(0, 0, 1), constraint_axis=(False, False, True),
|
||||
orient_type='GLOBAL', mirror=False, proportional='DISABLED',
|
||||
proportional_edit_falloff='SMOOTH', proportional_size=1
|
||||
)
|
||||
bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
plane = bpy.context.active_object
|
||||
# add new material
|
||||
planeMaterial = blend_data.materials.new("Plane_Material")
|
||||
bpy.ops.object.material_slot_add()
|
||||
plane.material_slots[0].material = planeMaterial
|
||||
# Material settings
|
||||
planeMaterial.preview_render_type = "CUBE"
|
||||
planeMaterial.diffuse_color = (0.2, 0.2, 0.2)
|
||||
planeMaterial.specular_color = (0.604, 0.465, 0.136)
|
||||
planeMaterial.specular_intensity = 0.3
|
||||
planeMaterial.ambient = 0
|
||||
planeMaterial.use_cubic = True
|
||||
planeMaterial.use_transparency = False
|
||||
planeMaterial.alpha = 0
|
||||
planeMaterial.use_transparent_shadows = True
|
||||
|
||||
except Exception as e:
|
||||
self.report({'WARNING'},
|
||||
"Some operations could not be performed (See Console for more info)")
|
||||
|
||||
print("\n[Add Advanced Objects]\nOperator: "
|
||||
"bi.add_scene\nError: {}".format(e))
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {"FINISHED"}
|
|
@ -1,142 +0,0 @@
|
|||
# gpl: author meta-androcto
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
|
||||
|
||||
class add_cycles_scene(Operator):
|
||||
bl_idname = "objects_cycles.add_scene"
|
||||
bl_label = "Create test scene"
|
||||
bl_description = "Cycles renderer Scene with Objects"
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
blend_data = context.blend_data
|
||||
|
||||
# add new scene
|
||||
bpy.ops.scene.new(type="NEW")
|
||||
scene = bpy.context.scene
|
||||
bpy.context.scene.render.engine = 'CYCLES'
|
||||
scene.name = "scene_object_cycles"
|
||||
|
||||
# render settings
|
||||
render = scene.render
|
||||
render.resolution_x = 1920
|
||||
render.resolution_y = 1080
|
||||
render.resolution_percentage = 50
|
||||
|
||||
# add new world
|
||||
world = bpy.data.worlds.new("Cycles_Object_World")
|
||||
scene.world = world
|
||||
world.use_sky_blend = True
|
||||
world.use_sky_paper = True
|
||||
world.horizon_color = (0.004393, 0.02121, 0.050)
|
||||
world.zenith_color = (0.03335, 0.227, 0.359)
|
||||
world.light_settings.use_ambient_occlusion = True
|
||||
world.light_settings.ao_factor = 0.25
|
||||
|
||||
# add camera
|
||||
bpy.ops.object.camera_add(
|
||||
location=(7.48113, -6.50764, 5.34367),
|
||||
rotation=(1.109319, 0.010817, 0.814928)
|
||||
)
|
||||
cam = bpy.context.active_object.data
|
||||
cam.lens = 35
|
||||
cam.display_size = 0.1
|
||||
bpy.ops.view3d.viewnumpad(type='CAMERA')
|
||||
|
||||
# add point lamp
|
||||
bpy.ops.object.light_add(
|
||||
type="POINT", location=(4.07625, 1.00545, 5.90386),
|
||||
rotation=(0.650328, 0.055217, 1.866391)
|
||||
)
|
||||
lamp1 = bpy.context.active_object.data
|
||||
lamp1.name = "Point_Right"
|
||||
lamp1.energy = 1.0
|
||||
lamp1.distance = 30.0
|
||||
lamp1.shadow_method = "RAY_SHADOW"
|
||||
lamp1.use_sphere = True
|
||||
|
||||
# add point lamp2
|
||||
bpy.ops.object.light_add(
|
||||
type="POINT", location=(-0.57101, -4.24586, 5.53674),
|
||||
rotation=(1.571, 0, 0.785)
|
||||
)
|
||||
lamp2 = bpy.context.active_object.data
|
||||
lamp2.name = "Point_Left"
|
||||
lamp2.energy = 1.0
|
||||
lamp2.distance = 30.0
|
||||
|
||||
# Add cube
|
||||
bpy.ops.mesh.primitive_cube_add()
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.mesh.subdivide(number_cuts=2)
|
||||
bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
cube = bpy.context.active_object
|
||||
|
||||
# add cube material
|
||||
cubeMaterial = blend_data.materials.new("Cycles_Cube_Material")
|
||||
bpy.ops.object.material_slot_add()
|
||||
cube.material_slots[0].material = cubeMaterial
|
||||
# Diffuse
|
||||
cubeMaterial.preview_render_type = "CUBE"
|
||||
cubeMaterial.diffuse_color = (1.000, 0.373, 0.00)
|
||||
# Cycles
|
||||
cubeMaterial.use_nodes = True
|
||||
|
||||
# Add monkey
|
||||
bpy.ops.mesh.primitive_monkey_add(location=(-0.1, 0.08901, 1.505))
|
||||
bpy.ops.transform.rotate(value=(1.15019), axis=(0, 0, 1))
|
||||
bpy.ops.transform.rotate(value=(-0.673882), axis=(0, 1, 0))
|
||||
bpy.ops.transform.rotate(value=-0.055, axis=(1, 0, 0))
|
||||
|
||||
bpy.ops.object.modifier_add(type='SUBSURF')
|
||||
bpy.ops.object.shade_smooth()
|
||||
monkey = bpy.context.active_object
|
||||
|
||||
# add monkey material
|
||||
monkeyMaterial = blend_data.materials.new("Cycles_Monkey_Material")
|
||||
bpy.ops.object.material_slot_add()
|
||||
monkey.material_slots[0].material = monkeyMaterial
|
||||
# Diffuse
|
||||
monkeyMaterial.preview_render_type = "MONKEY"
|
||||
monkeyMaterial.diffuse_color = (0.239, 0.288, 0.288)
|
||||
# Cycles
|
||||
monkeyMaterial.use_nodes = True
|
||||
|
||||
# Add plane
|
||||
bpy.ops.mesh.primitive_plane_add(
|
||||
radius=50, view_align=False,
|
||||
enter_editmode=False, location=(0, 0, -1)
|
||||
)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.transform.rotate(
|
||||
value=-0.8, axis=(0, 0, 1),
|
||||
constraint_axis=(False, False, True)
|
||||
)
|
||||
bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
plane = bpy.context.active_object
|
||||
|
||||
# add plane material
|
||||
planeMaterial = blend_data.materials.new("Cycles_Plane_Material")
|
||||
bpy.ops.object.material_slot_add()
|
||||
plane.material_slots[0].material = planeMaterial
|
||||
# Diffuse
|
||||
planeMaterial.preview_render_type = "FLAT"
|
||||
planeMaterial.diffuse_color = (0.2, 0.2, 0.2)
|
||||
# Cycles
|
||||
planeMaterial.use_nodes = True
|
||||
|
||||
except Exception as e:
|
||||
self.report({'WARNING'},
|
||||
"Some operations could not be performed (See Console for more info)")
|
||||
|
||||
print("\n[Add Advanced Objects]\nOperator: "
|
||||
"objects_cycles.add_scene\nError: {}".format(e))
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'FINISHED'}
|
|
@ -1,78 +0,0 @@
|
|||
# gpl: author meta-androcto
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
|
||||
|
||||
class add_texture_scene(Operator):
|
||||
bl_idname = "objects_texture.add_scene"
|
||||
bl_label = "Create test scene"
|
||||
bl_description = "Cycles renderer Scene: Camera aligned to a plane"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
blend_data = context.blend_data
|
||||
|
||||
# add new scene
|
||||
bpy.ops.scene.new(type="NEW")
|
||||
scene = bpy.context.scene
|
||||
bpy.context.scene.render.engine = 'CYCLES'
|
||||
scene.name = "scene_texture_cycles"
|
||||
|
||||
# render settings
|
||||
render = scene.render
|
||||
render.resolution_x = 1080
|
||||
render.resolution_y = 1080
|
||||
render.resolution_percentage = 100
|
||||
|
||||
# add new world
|
||||
world = bpy.data.worlds.new("Cycles_Textures_World")
|
||||
scene.world = world
|
||||
world.use_sky_blend = True
|
||||
world.use_sky_paper = True
|
||||
world.horizon_color = (0.004393, 0.02121, 0.050)
|
||||
world.zenith_color = (0.03335, 0.227, 0.359)
|
||||
world.light_settings.use_ambient_occlusion = True
|
||||
world.light_settings.ao_factor = 0.5
|
||||
|
||||
# add camera
|
||||
bpy.ops.view3d.viewnumpad(type='TOP')
|
||||
bpy.ops.object.camera_add(
|
||||
location=(0, 0, 2.1850), rotation=(0, 0, 0), view_align=True
|
||||
)
|
||||
cam = bpy.context.active_object.data
|
||||
cam.lens = 35
|
||||
cam.display_size = 0.1
|
||||
|
||||
# add plane
|
||||
bpy.ops.mesh.primitive_plane_add(enter_editmode=True, location=(0, 0, 0))
|
||||
bpy.ops.mesh.subdivide(number_cuts=10, smoothness=0)
|
||||
bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
plane = bpy.context.active_object
|
||||
|
||||
# add plane material
|
||||
planeMaterial = blend_data.materials.new("Cycles_Plane_Material")
|
||||
bpy.ops.object.material_slot_add()
|
||||
plane.material_slots[0].material = planeMaterial
|
||||
# Diffuse
|
||||
planeMaterial.preview_render_type = "FLAT"
|
||||
planeMaterial.diffuse_color = (0.2, 0.2, 0.2)
|
||||
# Cycles
|
||||
planeMaterial.use_nodes = True
|
||||
|
||||
# Back to Scene
|
||||
sc = bpy.context.scene
|
||||
bpy.ops.view3d.viewnumpad(type='CAMERA')
|
||||
|
||||
except Exception as e:
|
||||
self.report({'WARNING'},
|
||||
"Some operations could not be performed (See Console for more info)")
|
||||
|
||||
print("\n[Add Advanced Objects]\nOperator: "
|
||||
"objects_texture.add_scene\nError: {}".format(e))
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'FINISHED'}
|
|
@ -1,239 +0,0 @@
|
|||
# gpl: author Daniel Schalla
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
from bpy.props import (
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
)
|
||||
from math import (
|
||||
sin, cos,
|
||||
radians,
|
||||
sqrt,
|
||||
)
|
||||
|
||||
|
||||
class TriLighting(Operator):
|
||||
bl_idname = "object.trilighting"
|
||||
bl_label = "Tri-Lighting Creator"
|
||||
bl_description = ("Add 3 Point Lighting to Selected / Active Object\n"
|
||||
"Needs an existing Active Object")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
height: FloatProperty(
|
||||
name="Height",
|
||||
default=5
|
||||
)
|
||||
distance: FloatProperty(
|
||||
name="Distance",
|
||||
default=5,
|
||||
min=0.1,
|
||||
subtype="DISTANCE"
|
||||
)
|
||||
energy: IntProperty(
|
||||
name="Base Energy",
|
||||
default=3,
|
||||
min=1
|
||||
)
|
||||
contrast: IntProperty(
|
||||
name="Contrast",
|
||||
default=50,
|
||||
min=-100, max=100,
|
||||
subtype="PERCENTAGE"
|
||||
)
|
||||
leftangle: IntProperty(
|
||||
name="Left Angle",
|
||||
default=26,
|
||||
min=1, max=90,
|
||||
subtype="ANGLE"
|
||||
)
|
||||
rightangle: IntProperty(
|
||||
name="Right Angle",
|
||||
default=45,
|
||||
min=1, max=90,
|
||||
subtype="ANGLE"
|
||||
)
|
||||
backangle: IntProperty(
|
||||
name="Back Angle",
|
||||
default=235,
|
||||
min=90, max=270,
|
||||
subtype="ANGLE"
|
||||
)
|
||||
Light_Type_List = [
|
||||
('POINT', "Point", "Point Light"),
|
||||
('SUN', "Sun", "Sun Light"),
|
||||
('SPOT', "Spot", "Spot Light"),
|
||||
('HEMI', "Hemi", "Hemi Light"),
|
||||
('AREA', "Area", "Area Light")
|
||||
]
|
||||
primarytype: EnumProperty(
|
||||
attr='tl_type',
|
||||
name="Key Type",
|
||||
description="Choose the types of Key Lights you would like",
|
||||
items=Light_Type_List,
|
||||
default='HEMI'
|
||||
)
|
||||
secondarytype: EnumProperty(
|
||||
attr='tl_type',
|
||||
name="Fill + Back Type",
|
||||
description="Choose the types of secondary Lights you would like",
|
||||
items=Light_Type_List,
|
||||
default="POINT"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object is not None
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.label(text="Position:")
|
||||
col = layout.column(align=True)
|
||||
col.prop(self, "height")
|
||||
col.prop(self, "distance")
|
||||
|
||||
layout.label(text="Light:")
|
||||
col = layout.column(align=True)
|
||||
col.prop(self, "energy")
|
||||
col.prop(self, "contrast")
|
||||
|
||||
layout.label(text="Orientation:")
|
||||
col = layout.column(align=True)
|
||||
col.prop(self, "leftangle")
|
||||
col.prop(self, "rightangle")
|
||||
col.prop(self, "backangle")
|
||||
|
||||
col = layout.column()
|
||||
col.label(text="Key Light Type:")
|
||||
col.prop(self, "primarytype", text="")
|
||||
col.label(text="Fill + Back Type:")
|
||||
col.prop(self, "secondarytype", text="")
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
collection = context.collection
|
||||
scene = context.scene
|
||||
view = context.space_data
|
||||
if view.type == 'VIEW_3D' and not view.lock_camera_and_layers:
|
||||
camera = view.camera
|
||||
else:
|
||||
camera = scene.camera
|
||||
|
||||
if (camera is None):
|
||||
cam_data = bpy.data.cameras.new(name='Camera')
|
||||
cam_obj = bpy.data.objects.new(name='Camera', object_data=cam_data)
|
||||
collection.objects.link(cam_obj)
|
||||
scene.camera = cam_obj
|
||||
bpy.ops.view3d.camera_to_view()
|
||||
camera = cam_obj
|
||||
bpy.ops.view3d.viewnumpad(type='TOP')
|
||||
|
||||
obj = bpy.context.view_layer.objects.active
|
||||
|
||||
# Calculate Energy for each Lamp
|
||||
if(self.contrast > 0):
|
||||
keyEnergy = self.energy
|
||||
backEnergy = (self.energy / 100) * abs(self.contrast)
|
||||
fillEnergy = (self.energy / 100) * abs(self.contrast)
|
||||
else:
|
||||
keyEnergy = (self.energy / 100) * abs(self.contrast)
|
||||
backEnergy = self.energy
|
||||
fillEnergy = self.energy
|
||||
|
||||
# Calculate Direction for each Lamp
|
||||
|
||||
# Calculate current Distance and get Delta
|
||||
obj_position = obj.location
|
||||
cam_position = camera.location
|
||||
|
||||
delta_position = cam_position - obj_position
|
||||
vector_length = sqrt(
|
||||
(pow(delta_position.x, 2) +
|
||||
pow(delta_position.y, 2) +
|
||||
pow(delta_position.z, 2))
|
||||
)
|
||||
if not vector_length:
|
||||
# division by zero most likely
|
||||
self.report({'WARNING'},
|
||||
"Operation Cancelled. No viable object in the scene")
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
single_vector = (1 / vector_length) * delta_position
|
||||
|
||||
# Calc back position
|
||||
singleback_vector = single_vector.copy()
|
||||
singleback_vector.x = cos(radians(self.backangle)) * single_vector.x + \
|
||||
(-sin(radians(self.backangle)) * single_vector.y)
|
||||
|
||||
singleback_vector.y = sin(radians(self.backangle)) * single_vector.x + \
|
||||
(cos(radians(self.backangle)) * single_vector.y)
|
||||
|
||||
backx = obj_position.x + self.distance * singleback_vector.x
|
||||
backy = obj_position.y + self.distance * singleback_vector.y
|
||||
|
||||
backData = bpy.data.lights.new(name="TriLamp-Back", type=self.secondarytype)
|
||||
backData.energy = backEnergy
|
||||
|
||||
backLamp = bpy.data.objects.new(name="TriLamp-Back", object_data=backData)
|
||||
collection.objects.link(backLamp)
|
||||
backLamp.location = (backx, backy, self.height)
|
||||
|
||||
trackToBack = backLamp.constraints.new(type="TRACK_TO")
|
||||
trackToBack.target = obj
|
||||
trackToBack.track_axis = "TRACK_NEGATIVE_Z"
|
||||
trackToBack.up_axis = "UP_Y"
|
||||
|
||||
# Calc right position
|
||||
singleright_vector = single_vector.copy()
|
||||
singleright_vector.x = cos(radians(self.rightangle)) * single_vector.x + \
|
||||
(-sin(radians(self.rightangle)) * single_vector.y)
|
||||
|
||||
singleright_vector.y = sin(radians(self.rightangle)) * single_vector.x + \
|
||||
(cos(radians(self.rightangle)) * single_vector.y)
|
||||
|
||||
rightx = obj_position.x + self.distance * singleright_vector.x
|
||||
righty = obj_position.y + self.distance * singleright_vector.y
|
||||
|
||||
rightData = bpy.data.lights.new(name="TriLamp-Fill", type=self.secondarytype)
|
||||
rightData.energy = fillEnergy
|
||||
rightLamp = bpy.data.objects.new(name="TriLamp-Fill", object_data=rightData)
|
||||
collection.objects.link(rightLamp)
|
||||
rightLamp.location = (rightx, righty, self.height)
|
||||
trackToRight = rightLamp.constraints.new(type="TRACK_TO")
|
||||
trackToRight.target = obj
|
||||
trackToRight.track_axis = "TRACK_NEGATIVE_Z"
|
||||
trackToRight.up_axis = "UP_Y"
|
||||
|
||||
# Calc left position
|
||||
singleleft_vector = single_vector.copy()
|
||||
singleleft_vector.x = cos(radians(-self.leftangle)) * single_vector.x + \
|
||||
(-sin(radians(-self.leftangle)) * single_vector.y)
|
||||
singleleft_vector.y = sin(radians(-self.leftangle)) * single_vector.x + \
|
||||
(cos(radians(-self.leftangle)) * single_vector.y)
|
||||
leftx = obj_position.x + self.distance * singleleft_vector.x
|
||||
lefty = obj_position.y + self.distance * singleleft_vector.y
|
||||
|
||||
leftData = bpy.data.lights.new(name="TriLamp-Key", type=self.primarytype)
|
||||
leftData.energy = keyEnergy
|
||||
|
||||
leftLamp = bpy.data.objects.new(name="TriLamp-Key", object_data=leftData)
|
||||
collection.objects.link(leftLamp)
|
||||
leftLamp.location = (leftx, lefty, self.height)
|
||||
trackToLeft = leftLamp.constraints.new(type="TRACK_TO")
|
||||
trackToLeft.target = obj
|
||||
trackToLeft.track_axis = "TRACK_NEGATIVE_Z"
|
||||
trackToLeft.up_axis = "UP_Y"
|
||||
|
||||
except Exception as e:
|
||||
self.report({'WARNING'},
|
||||
"Some operations could not be performed (See Console for more info)")
|
||||
|
||||
print("\n[Add Advanced Objects]\nOperator: "
|
||||
"object.trilighting\nError: {}".format(e))
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'FINISHED'}
|
|
@ -1,998 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Voronoi diagram calculator/ Delaunay triangulator
|
||||
#
|
||||
# - Voronoi Diagram Sweepline algorithm and C code by Steven Fortune,
|
||||
# 1987, http://ect.bell-labs.com/who/sjf/
|
||||
# - Python translation to file voronoi.py by Bill Simons, 2005, http://www.oxfish.com/
|
||||
# - Additional changes for QGIS by Carson Farmer added November 2010
|
||||
# - 2012 Ported to Python 3 and additional clip functions by domlysz at gmail.com
|
||||
#
|
||||
# Calculate Delaunay triangulation or the Voronoi polygons for a set of
|
||||
# 2D input points.
|
||||
#
|
||||
# Derived from code bearing the following notice:
|
||||
#
|
||||
# The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T
|
||||
# Bell Laboratories.
|
||||
# Permission to use, copy, modify, and distribute this software for any
|
||||
# purpose without fee is hereby granted, provided that this entire notice
|
||||
# is included in all copies of any software which is or includes a copy
|
||||
# or modification of this software and in all copies of the supporting
|
||||
# documentation for such software.
|
||||
# THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
# WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
# REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
# OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
#
|
||||
# Comments were incorporated from Shane O'Sullivan's translation of the
|
||||
# original code into C++ (http://mapviewer.skynet.ie/voronoi.html)
|
||||
#
|
||||
# Steve Fortune's homepage: http://netlib.bell-labs.com/cm/cs/who/sjf/index.html
|
||||
#
|
||||
# For programmatic use, two functions are available:
|
||||
#
|
||||
# computeVoronoiDiagram(points, xBuff, yBuff, polygonsOutput=False, formatOutput=False):
|
||||
# Takes :
|
||||
# - a list of point objects (which must have x and y fields).
|
||||
# - x and y buffer values which are the expansion percentages of the
|
||||
# bounding box rectangle including all input points.
|
||||
# Returns :
|
||||
# - With default options :
|
||||
# A list of 2-tuples, representing the two points of each Voronoi diagram edge.
|
||||
# Each point contains 2-tuples which are the x,y coordinates of point.
|
||||
# if formatOutput is True, returns :
|
||||
# - a list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices.
|
||||
# - and a list of 2-tuples (v1, v2) representing edges of the Voronoi diagram.
|
||||
# v1 and v2 are the indices of the vertices at the end of the edge.
|
||||
# - If polygonsOutput option is True, returns :
|
||||
# A dictionary of polygons, keys are the indices of the input points,
|
||||
# values contains n-tuples representing the n points of each Voronoi diagram polygon.
|
||||
# Each point contains 2-tuples which are the x,y coordinates of point.
|
||||
# if formatOutput is True, returns :
|
||||
# - A list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices.
|
||||
# - and a dictionary of input points indices. Values contains n-tuples representing
|
||||
# the n points of each Voronoi diagram polygon.
|
||||
# Each tuple contains the vertex indices of the polygon vertices.
|
||||
#
|
||||
# computeDelaunayTriangulation(points):
|
||||
# Takes a list of point objects (which must have x and y fields).
|
||||
# Returns a list of 3-tuples: the indices of the points that form a Delaunay triangle.
|
||||
|
||||
import bpy
|
||||
import math
|
||||
|
||||
# Globals
|
||||
TOLERANCE = 1e-9
|
||||
BIG_FLOAT = 1e38
|
||||
|
||||
|
||||
class Context(object):
|
||||
|
||||
def __init__(self):
|
||||
self.doPrint = 0
|
||||
self.debug = 0
|
||||
|
||||
# tuple (xmin, xmax, ymin, ymax)
|
||||
self.extent = ()
|
||||
self.triangulate = False
|
||||
# list of vertex 2-tuples: (x,y)
|
||||
self.vertices = []
|
||||
# equation of line 3-tuple (a b c), for the equation of the line a*x+b*y = c
|
||||
self.lines = []
|
||||
|
||||
# edge 3-tuple: (line index, vertex 1 index, vertex 2 index)
|
||||
# if either vertex index is -1, the edge extends to infinity
|
||||
self.edges = []
|
||||
# 3-tuple of vertex indices
|
||||
self.triangles = []
|
||||
# a dict of site:[edges] pairs
|
||||
self.polygons = {}
|
||||
|
||||
|
||||
# Clip functions #
|
||||
def getClipEdges(self):
|
||||
xmin, xmax, ymin, ymax = self.extent
|
||||
clipEdges = []
|
||||
for edge in self.edges:
|
||||
equation = self.lines[edge[0]] # line equation
|
||||
if edge[1] != -1 and edge[2] != -1: # finite line
|
||||
x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1]
|
||||
x2, y2 = self.vertices[edge[2]][0], self.vertices[edge[2]][1]
|
||||
pt1, pt2 = (x1, y1), (x2, y2)
|
||||
inExtentP1, inExtentP2 = self.inExtent(x1, y1), self.inExtent(x2, y2)
|
||||
if inExtentP1 and inExtentP2:
|
||||
clipEdges.append((pt1, pt2))
|
||||
elif inExtentP1 and not inExtentP2:
|
||||
pt2 = self.clipLine(x1, y1, equation, leftDir=False)
|
||||
clipEdges.append((pt1, pt2))
|
||||
elif not inExtentP1 and inExtentP2:
|
||||
pt1 = self.clipLine(x2, y2, equation, leftDir=True)
|
||||
clipEdges.append((pt1, pt2))
|
||||
else: # infinite line
|
||||
if edge[1] != -1:
|
||||
x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1]
|
||||
leftDir = False
|
||||
else:
|
||||
x1, y1 = self.vertices[edge[2]][0], self.vertices[edge[2]][1]
|
||||
leftDir = True
|
||||
if self.inExtent(x1, y1):
|
||||
pt1 = (x1, y1)
|
||||
pt2 = self.clipLine(x1, y1, equation, leftDir)
|
||||
clipEdges.append((pt1, pt2))
|
||||
return clipEdges
|
||||
|
||||
def getClipPolygons(self, closePoly):
|
||||
xmin, xmax, ymin, ymax = self.extent
|
||||
poly = {}
|
||||
for inPtsIdx, edges in self.polygons.items():
|
||||
clipEdges = []
|
||||
for edge in edges:
|
||||
equation = self.lines[edge[0]] # line equation
|
||||
if edge[1] != -1 and edge[2] != -1: # finite line
|
||||
x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1]
|
||||
x2, y2 = self.vertices[edge[2]][0], self.vertices[edge[2]][1]
|
||||
pt1, pt2 = (x1, y1), (x2, y2)
|
||||
inExtentP1, inExtentP2 = self.inExtent(x1, y1), self.inExtent(x2, y2)
|
||||
if inExtentP1 and inExtentP2:
|
||||
clipEdges.append((pt1, pt2))
|
||||
elif inExtentP1 and not inExtentP2:
|
||||
pt2 = self.clipLine(x1, y1, equation, leftDir=False)
|
||||
clipEdges.append((pt1, pt2))
|
||||
elif not inExtentP1 and inExtentP2:
|
||||
pt1 = self.clipLine(x2, y2, equation, leftDir=True)
|
||||
clipEdges.append((pt1, pt2))
|
||||
else: # infinite line
|
||||
if edge[1] != -1:
|
||||
x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1]
|
||||
leftDir = False
|
||||
else:
|
||||
x1, y1 = self.vertices[edge[2]][0], self.vertices[edge[2]][1]
|
||||
leftDir = True
|
||||
if self.inExtent(x1, y1):
|
||||
pt1 = (x1, y1)
|
||||
pt2 = self.clipLine(x1, y1, equation, leftDir)
|
||||
clipEdges.append((pt1, pt2))
|
||||
# create polygon definition from edges and check if polygon is completely closed
|
||||
polyPts, complete = self.orderPts(clipEdges)
|
||||
if not complete:
|
||||
startPt = polyPts[0]
|
||||
endPt = polyPts[-1]
|
||||
# if start & end points are collinear then they are along an extent border
|
||||
if startPt[0] == endPt[0] or startPt[1] == endPt[1]:
|
||||
polyPts.append(polyPts[0]) # simple close
|
||||
else: # close at extent corner
|
||||
# upper left
|
||||
if (startPt[0] == xmin and endPt[1] == ymax) or (endPt[0] == xmin and startPt[1] == ymax):
|
||||
polyPts.append((xmin, ymax)) # corner point
|
||||
polyPts.append(polyPts[0]) # close polygon
|
||||
# upper right
|
||||
if (startPt[0] == xmax and endPt[1] == ymax) or (endPt[0] == xmax and startPt[1] == ymax):
|
||||
polyPts.append((xmax, ymax))
|
||||
polyPts.append(polyPts[0])
|
||||
# bottom right
|
||||
if (startPt[0] == xmax and endPt[1] == ymin) or (endPt[0] == xmax and startPt[1] == ymin):
|
||||
polyPts.append((xmax, ymin))
|
||||
polyPts.append(polyPts[0])
|
||||
# bottom left
|
||||
if (startPt[0] == xmin and endPt[1] == ymin) or (endPt[0] == xmin and startPt[1] == ymin):
|
||||
polyPts.append((xmin, ymin))
|
||||
polyPts.append(polyPts[0])
|
||||
if not closePoly: # unclose polygon
|
||||
polyPts = polyPts[:-1]
|
||||
poly[inPtsIdx] = polyPts
|
||||
return poly
|
||||
|
||||
def clipLine(self, x1, y1, equation, leftDir):
|
||||
xmin, xmax, ymin, ymax = self.extent
|
||||
a, b, c = equation
|
||||
if b == 0: # vertical line
|
||||
if leftDir: # left is bottom of vertical line
|
||||
return (x1, ymax)
|
||||
else:
|
||||
return (x1, ymin)
|
||||
elif a == 0: # horizontal line
|
||||
if leftDir:
|
||||
return (xmin, y1)
|
||||
else:
|
||||
return (xmax, y1)
|
||||
else:
|
||||
y2_at_xmin = (c - a * xmin) / b
|
||||
y2_at_xmax = (c - a * xmax) / b
|
||||
x2_at_ymin = (c - b * ymin) / a
|
||||
x2_at_ymax = (c - b * ymax) / a
|
||||
intersectPts = []
|
||||
if ymin <= y2_at_xmin <= ymax: # valid intersect point
|
||||
intersectPts.append((xmin, y2_at_xmin))
|
||||
if ymin <= y2_at_xmax <= ymax:
|
||||
intersectPts.append((xmax, y2_at_xmax))
|
||||
if xmin <= x2_at_ymin <= xmax:
|
||||
intersectPts.append((x2_at_ymin, ymin))
|
||||
if xmin <= x2_at_ymax <= xmax:
|
||||
intersectPts.append((x2_at_ymax, ymax))
|
||||
# delete duplicate (happens if intersect point is at extent corner)
|
||||
intersectPts = set(intersectPts)
|
||||
# choose target intersect point
|
||||
if leftDir:
|
||||
pt = min(intersectPts) # smaller x value
|
||||
else:
|
||||
pt = max(intersectPts)
|
||||
return pt
|
||||
|
||||
def inExtent(self, x, y):
|
||||
xmin, xmax, ymin, ymax = self.extent
|
||||
return x >= xmin and x <= xmax and y >= ymin and y <= ymax
|
||||
|
||||
def orderPts(self, edges):
|
||||
poly = [] # returned polygon points list [pt1, pt2, pt3, pt4 ....]
|
||||
pts = []
|
||||
# get points list
|
||||
for edge in edges:
|
||||
pts.extend([pt for pt in edge])
|
||||
# try to get start & end point
|
||||
try:
|
||||
startPt, endPt = [pt for pt in pts if pts.count(pt) < 2] # start and end point aren't duplicate
|
||||
except: # all points are duplicate --> polygon is complete --> append some or other edge points
|
||||
complete = True
|
||||
firstIdx = 0
|
||||
poly.append(edges[0][0])
|
||||
poly.append(edges[0][1])
|
||||
else: # incomplete --> append the first edge points
|
||||
complete = False
|
||||
# search first edge
|
||||
for i, edge in enumerate(edges):
|
||||
if startPt in edge: # find
|
||||
firstIdx = i
|
||||
break
|
||||
poly.append(edges[firstIdx][0])
|
||||
poly.append(edges[firstIdx][1])
|
||||
if poly[0] != startPt:
|
||||
poly.reverse()
|
||||
# append next points in list
|
||||
del edges[firstIdx]
|
||||
while edges: # all points will be treated when edges list will be empty
|
||||
currentPt = poly[-1] # last item
|
||||
for i, edge in enumerate(edges):
|
||||
if currentPt == edge[0]:
|
||||
poly.append(edge[1])
|
||||
break
|
||||
elif currentPt == edge[1]:
|
||||
poly.append(edge[0])
|
||||
break
|
||||
del edges[i]
|
||||
return poly, complete
|
||||
|
||||
def setClipBuffer(self, xpourcent, ypourcent):
|
||||
xmin, xmax, ymin, ymax = self.extent
|
||||
width = xmax - xmin
|
||||
height = ymax - ymin
|
||||
xmin = xmin - width * xpourcent / 100
|
||||
xmax = xmax + width * xpourcent / 100
|
||||
ymin = ymin - height * ypourcent / 100
|
||||
ymax = ymax + height * ypourcent / 100
|
||||
self.extent = xmin, xmax, ymin, ymax
|
||||
|
||||
# End clip functions #
|
||||
|
||||
def outSite(self, s):
|
||||
if(self.debug):
|
||||
print("site (%d) at %f %f" % (s.sitenum, s.x, s.y))
|
||||
elif(self.triangulate):
|
||||
pass
|
||||
elif(self.doPrint):
|
||||
print("s %f %f" % (s.x, s.y))
|
||||
|
||||
def outVertex(self, s):
|
||||
self.vertices.append((s.x, s.y))
|
||||
if(self.debug):
|
||||
print("vertex(%d) at %f %f" % (s.sitenum, s.x, s.y))
|
||||
elif(self.triangulate):
|
||||
pass
|
||||
elif(self.doPrint):
|
||||
print("v %f %f" % (s.x, s.y))
|
||||
|
||||
def outTriple(self, s1, s2, s3):
|
||||
self.triangles.append((s1.sitenum, s2.sitenum, s3.sitenum))
|
||||
if (self.debug):
|
||||
print("circle through left=%d right=%d bottom=%d" % (s1.sitenum, s2.sitenum, s3.sitenum))
|
||||
elif (self.triangulate and self.doPrint):
|
||||
print("%d %d %d" % (s1.sitenum, s2.sitenum, s3.sitenum))
|
||||
|
||||
def outBisector(self, edge):
|
||||
self.lines.append((edge.a, edge.b, edge.c))
|
||||
if (self.debug):
|
||||
print("line(%d) %gx+%gy=%g, bisecting %d %d" % (edge.edgenum, edge.a, edge.b,
|
||||
edge.c, edge.reg[0].sitenum,
|
||||
edge.reg[1].sitenum)
|
||||
)
|
||||
elif(self.doPrint):
|
||||
print("l %f %f %f" % (edge.a, edge.b, edge.c))
|
||||
|
||||
def outEdge(self, edge):
|
||||
sitenumL = -1
|
||||
if edge.ep[Edge.LE] is not None:
|
||||
sitenumL = edge.ep[Edge.LE].sitenum
|
||||
sitenumR = -1
|
||||
if edge.ep[Edge.RE] is not None:
|
||||
sitenumR = edge.ep[Edge.RE].sitenum
|
||||
|
||||
# polygons dict add by CF
|
||||
if edge.reg[0].sitenum not in self.polygons:
|
||||
self.polygons[edge.reg[0].sitenum] = []
|
||||
if edge.reg[1].sitenum not in self.polygons:
|
||||
self.polygons[edge.reg[1].sitenum] = []
|
||||
self.polygons[edge.reg[0].sitenum].append((edge.edgenum, sitenumL, sitenumR))
|
||||
self.polygons[edge.reg[1].sitenum].append((edge.edgenum, sitenumL, sitenumR))
|
||||
|
||||
self.edges.append((edge.edgenum, sitenumL, sitenumR))
|
||||
|
||||
if (not self.triangulate):
|
||||
if (self.doPrint):
|
||||
print("e %d" % edge.edgenum)
|
||||
print(" %d " % sitenumL)
|
||||
print("%d" % sitenumR)
|
||||
|
||||
|
||||
def voronoi(siteList, context):
|
||||
context.extent = siteList.extent
|
||||
edgeList = EdgeList(siteList.xmin, siteList.xmax, len(siteList))
|
||||
priorityQ = PriorityQueue(siteList.ymin, siteList.ymax, len(siteList))
|
||||
siteIter = siteList.iterator()
|
||||
|
||||
bottomsite = siteIter.next()
|
||||
context.outSite(bottomsite)
|
||||
newsite = siteIter.next()
|
||||
minpt = Site(-BIG_FLOAT, -BIG_FLOAT)
|
||||
while True:
|
||||
if not priorityQ.isEmpty():
|
||||
minpt = priorityQ.getMinPt()
|
||||
|
||||
if (newsite and (priorityQ.isEmpty() or newsite < minpt)):
|
||||
# newsite is smallest - this is a site event
|
||||
context.outSite(newsite)
|
||||
|
||||
# get first Halfedge to the LEFT and RIGHT of the new site
|
||||
lbnd = edgeList.leftbnd(newsite)
|
||||
rbnd = lbnd.right
|
||||
|
||||
# if this halfedge has no edge, bot = bottom site (whatever that is)
|
||||
# create a new edge that bisects
|
||||
bot = lbnd.rightreg(bottomsite)
|
||||
edge = Edge.bisect(bot, newsite)
|
||||
context.outBisector(edge)
|
||||
|
||||
# create a new Halfedge, setting its pm field to 0 and insert
|
||||
# this new bisector edge between the left and right vectors in
|
||||
# a linked list
|
||||
bisector = Halfedge(edge, Edge.LE)
|
||||
edgeList.insert(lbnd, bisector)
|
||||
|
||||
# if the new bisector intersects with the left edge, remove
|
||||
# the left edge's vertex, and put in the new one
|
||||
p = lbnd.intersect(bisector)
|
||||
if p is not None:
|
||||
priorityQ.delete(lbnd)
|
||||
priorityQ.insert(lbnd, p, newsite.distance(p))
|
||||
|
||||
# create a new Halfedge, setting its pm field to 1
|
||||
# insert the new Halfedge to the right of the original bisector
|
||||
lbnd = bisector
|
||||
bisector = Halfedge(edge, Edge.RE)
|
||||
edgeList.insert(lbnd, bisector)
|
||||
|
||||
# if this new bisector intersects with the right Halfedge
|
||||
p = bisector.intersect(rbnd)
|
||||
if p is not None:
|
||||
# push the Halfedge into the ordered linked list of vertices
|
||||
priorityQ.insert(bisector, p, newsite.distance(p))
|
||||
|
||||
newsite = siteIter.next()
|
||||
|
||||
elif not priorityQ.isEmpty():
|
||||
# intersection is smallest - this is a vector (circle) event
|
||||
# pop the Halfedge with the lowest vector off the ordered list of
|
||||
# vectors. Get the Halfedge to the left and right of the above HE
|
||||
# and also the Halfedge to the right of the right HE
|
||||
lbnd = priorityQ.popMinHalfedge()
|
||||
llbnd = lbnd.left
|
||||
rbnd = lbnd.right
|
||||
rrbnd = rbnd.right
|
||||
|
||||
# get the Site to the left of the left HE and to the right of
|
||||
# the right HE which it bisects
|
||||
bot = lbnd.leftreg(bottomsite)
|
||||
top = rbnd.rightreg(bottomsite)
|
||||
|
||||
# output the triple of sites, stating that a circle goes through them
|
||||
mid = lbnd.rightreg(bottomsite)
|
||||
context.outTriple(bot, top, mid)
|
||||
|
||||
# get the vertex that caused this event and set the vertex number
|
||||
# couldn't do this earlier since we didn't know when it would be processed
|
||||
v = lbnd.vertex
|
||||
siteList.setSiteNumber(v)
|
||||
context.outVertex(v)
|
||||
|
||||
# set the endpoint of the left and right Halfedge to be this vector
|
||||
if lbnd.edge.setEndpoint(lbnd.pm, v):
|
||||
context.outEdge(lbnd.edge)
|
||||
|
||||
if rbnd.edge.setEndpoint(rbnd.pm, v):
|
||||
context.outEdge(rbnd.edge)
|
||||
|
||||
# delete the lowest HE, remove all vertex events to do with the
|
||||
# right HE and delete the right HE
|
||||
edgeList.delete(lbnd)
|
||||
priorityQ.delete(rbnd)
|
||||
edgeList.delete(rbnd)
|
||||
|
||||
# if the site to the left of the event is higher than the Site
|
||||
# to the right of it, then swap them and set 'pm' to RIGHT
|
||||
pm = Edge.LE
|
||||
if bot.y > top.y:
|
||||
bot, top = top, bot
|
||||
pm = Edge.RE
|
||||
|
||||
# Create an Edge (or line) that is between the two Sites. This
|
||||
# creates the formula of the line, and assigns a line number to it
|
||||
edge = Edge.bisect(bot, top)
|
||||
context.outBisector(edge)
|
||||
|
||||
# create a HE from the edge
|
||||
bisector = Halfedge(edge, pm)
|
||||
|
||||
# insert the new bisector to the right of the left HE
|
||||
# set one endpoint to the new edge to be the vector point 'v'
|
||||
# If the site to the left of this bisector is higher than the right
|
||||
# Site, then this endpoint is put in position 0; otherwise in pos 1
|
||||
edgeList.insert(llbnd, bisector)
|
||||
if edge.setEndpoint(Edge.RE - pm, v):
|
||||
context.outEdge(edge)
|
||||
|
||||
# if left HE and the new bisector don't intersect, then delete
|
||||
# the left HE, and reinsert it
|
||||
p = llbnd.intersect(bisector)
|
||||
if p is not None:
|
||||
priorityQ.delete(llbnd)
|
||||
priorityQ.insert(llbnd, p, bot.distance(p))
|
||||
|
||||
# if right HE and the new bisector don't intersect, then reinsert it
|
||||
p = bisector.intersect(rrbnd)
|
||||
if p is not None:
|
||||
priorityQ.insert(bisector, p, bot.distance(p))
|
||||
else:
|
||||
break
|
||||
|
||||
he = edgeList.leftend.right
|
||||
while he is not edgeList.rightend:
|
||||
context.outEdge(he.edge)
|
||||
he = he.right
|
||||
Edge.EDGE_NUM = 0 # CF
|
||||
|
||||
|
||||
def isEqual(a, b, relativeError=TOLERANCE):
|
||||
# is nearly equal to within the allowed relative error
|
||||
norm = max(abs(a), abs(b))
|
||||
return (norm < relativeError) or (abs(a - b) < (relativeError * norm))
|
||||
|
||||
|
||||
class Site(object):
|
||||
|
||||
def __init__(self, x=0.0, y=0.0, sitenum=0):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.sitenum = sitenum
|
||||
|
||||
def dump(self):
|
||||
print("Site #%d (%g, %g)" % (self.sitenum, self.x, self.y))
|
||||
|
||||
def __lt__(self, other):
|
||||
if self.y < other.y:
|
||||
return True
|
||||
elif self.y > other.y:
|
||||
return False
|
||||
elif self.x < other.x:
|
||||
return True
|
||||
elif self.x > other.x:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def __eq__(self, other):
|
||||
if self.y == other.y and self.x == other.x:
|
||||
return True
|
||||
|
||||
def distance(self, other):
|
||||
dx = self.x - other.x
|
||||
dy = self.y - other.y
|
||||
return math.sqrt(dx * dx + dy * dy)
|
||||
|
||||
|
||||
class Edge(object):
|
||||
LE = 0 # left end indice --> edge.ep[Edge.LE]
|
||||
RE = 1 # right end indice
|
||||
EDGE_NUM = 0
|
||||
DELETED = {} # marker value
|
||||
|
||||
def __init__(self):
|
||||
self.a = 0.0 # equation of the line a*x+b*y = c
|
||||
self.b = 0.0
|
||||
self.c = 0.0
|
||||
self.ep = [None, None] # end point (2 tuples of site)
|
||||
self.reg = [None, None]
|
||||
self.edgenum = 0
|
||||
|
||||
def dump(self):
|
||||
print("(#%d a=%g, b=%g, c=%g)" % (self.edgenum, self.a, self.b, self.c))
|
||||
print("ep", self.ep)
|
||||
print("reg", self.reg)
|
||||
|
||||
def setEndpoint(self, lrFlag, site):
|
||||
self.ep[lrFlag] = site
|
||||
if self.ep[Edge.RE - lrFlag] is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def bisect(s1, s2):
|
||||
newedge = Edge()
|
||||
newedge.reg[0] = s1 # store the sites that this edge is bisecting
|
||||
newedge.reg[1] = s2
|
||||
|
||||
# to begin with, there are no endpoints on the bisector - it goes to infinity
|
||||
# ep[0] and ep[1] are None
|
||||
|
||||
# get the difference in x dist between the sites
|
||||
dx = float(s2.x - s1.x)
|
||||
dy = float(s2.y - s1.y)
|
||||
adx = abs(dx) # make sure that the difference in positive
|
||||
ady = abs(dy)
|
||||
|
||||
# get the slope of the line
|
||||
newedge.c = float(s1.x * dx + s1.y * dy + (dx * dx + dy * dy) * 0.5)
|
||||
if adx > ady:
|
||||
# set formula of line, with x fixed to 1
|
||||
newedge.a = 1.0
|
||||
newedge.b = dy / dx
|
||||
newedge.c /= dx
|
||||
else:
|
||||
# set formula of line, with y fixed to 1
|
||||
newedge.b = 1.0
|
||||
newedge.a = dx / dy
|
||||
newedge.c /= dy
|
||||
|
||||
newedge.edgenum = Edge.EDGE_NUM
|
||||
Edge.EDGE_NUM += 1
|
||||
return newedge
|
||||
|
||||
|
||||
class Halfedge(object):
|
||||
|
||||
def __init__(self, edge=None, pm=Edge.LE):
|
||||
self.left = None # left Halfedge in the edge list
|
||||
self.right = None # right Halfedge in the edge list
|
||||
self.qnext = None # priority queue linked list pointer
|
||||
self.edge = edge # edge list Edge
|
||||
self.pm = pm
|
||||
self.vertex = None # Site()
|
||||
self.ystar = BIG_FLOAT
|
||||
|
||||
def dump(self):
|
||||
print("Halfedge--------------------------")
|
||||
print("left: ", self.left)
|
||||
print("right: ", self.right)
|
||||
print("edge: ", self.edge)
|
||||
print("pm: ", self.pm)
|
||||
print("vertex: "),
|
||||
if self.vertex:
|
||||
self.vertex.dump()
|
||||
else:
|
||||
print("None")
|
||||
print("ystar: ", self.ystar)
|
||||
|
||||
def __lt__(self, other):
|
||||
if self.ystar < other.ystar:
|
||||
return True
|
||||
elif self.ystar > other.ystar:
|
||||
return False
|
||||
elif self.vertex.x < other.vertex.x:
|
||||
return True
|
||||
elif self.vertex.x > other.vertex.x:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def __eq__(self, other):
|
||||
if self.ystar == other.ystar and self.vertex.x == other.vertex.x:
|
||||
return True
|
||||
|
||||
def leftreg(self, default):
|
||||
if not self.edge:
|
||||
return default
|
||||
elif self.pm == Edge.LE:
|
||||
return self.edge.reg[Edge.LE]
|
||||
else:
|
||||
return self.edge.reg[Edge.RE]
|
||||
|
||||
def rightreg(self, default):
|
||||
if not self.edge:
|
||||
return default
|
||||
elif self.pm == Edge.LE:
|
||||
return self.edge.reg[Edge.RE]
|
||||
else:
|
||||
return self.edge.reg[Edge.LE]
|
||||
|
||||
# returns True if p is to right of halfedge self
|
||||
def isPointRightOf(self, pt):
|
||||
e = self.edge
|
||||
topsite = e.reg[1]
|
||||
right_of_site = pt.x > topsite.x
|
||||
|
||||
if(right_of_site and self.pm == Edge.LE):
|
||||
return True
|
||||
|
||||
if(not right_of_site and self.pm == Edge.RE):
|
||||
return False
|
||||
|
||||
if(e.a == 1.0):
|
||||
dyp = pt.y - topsite.y
|
||||
dxp = pt.x - topsite.x
|
||||
fast = 0
|
||||
if ((not right_of_site and e.b < 0.0) or (right_of_site and e.b >= 0.0)):
|
||||
above = dyp >= e.b * dxp
|
||||
fast = above
|
||||
else:
|
||||
above = pt.x + pt.y * e.b > e.c
|
||||
if(e.b < 0.0):
|
||||
above = not above
|
||||
if (not above):
|
||||
fast = 1
|
||||
if (not fast):
|
||||
dxs = topsite.x - (e.reg[0]).x
|
||||
above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1.0 + 2.0 * dxp / dxs + e.b * e.b)
|
||||
if(e.b < 0.0):
|
||||
above = not above
|
||||
else: # e.b == 1.0
|
||||
yl = e.c - e.a * pt.x
|
||||
t1 = pt.y - yl
|
||||
t2 = pt.x - topsite.x
|
||||
t3 = yl - topsite.y
|
||||
above = t1 * t1 > t2 * t2 + t3 * t3
|
||||
|
||||
if(self.pm == Edge.LE):
|
||||
return above
|
||||
else:
|
||||
return not above
|
||||
|
||||
# create a new site where the Halfedges el1 and el2 intersect
|
||||
def intersect(self, other):
|
||||
e1 = self.edge
|
||||
e2 = other.edge
|
||||
if (e1 is None) or (e2 is None):
|
||||
return None
|
||||
|
||||
# if the two edges bisect the same parent return None
|
||||
if e1.reg[1] is e2.reg[1]:
|
||||
return None
|
||||
|
||||
d = e1.a * e2.b - e1.b * e2.a
|
||||
if isEqual(d, 0.0):
|
||||
return None
|
||||
|
||||
xint = (e1.c * e2.b - e2.c * e1.b) / d
|
||||
yint = (e2.c * e1.a - e1.c * e2.a) / d
|
||||
if e1.reg[1] < e2.reg[1]:
|
||||
he = self
|
||||
e = e1
|
||||
else:
|
||||
he = other
|
||||
e = e2
|
||||
|
||||
rightOfSite = xint >= e.reg[1].x
|
||||
if((rightOfSite and he.pm == Edge.LE) or
|
||||
(not rightOfSite and he.pm == Edge.RE)):
|
||||
return None
|
||||
|
||||
# create a new site at the point of intersection - this is a new
|
||||
# vector event waiting to happen
|
||||
return Site(xint, yint)
|
||||
|
||||
|
||||
class EdgeList(object):
|
||||
|
||||
def __init__(self, xmin, xmax, nsites):
|
||||
if xmin > xmax:
|
||||
xmin, xmax = xmax, xmin
|
||||
self.hashsize = int(2 * math.sqrt(nsites + 4))
|
||||
|
||||
self.xmin = xmin
|
||||
self.deltax = float(xmax - xmin)
|
||||
self.hash = [None] * self.hashsize
|
||||
|
||||
self.leftend = Halfedge()
|
||||
self.rightend = Halfedge()
|
||||
self.leftend.right = self.rightend
|
||||
self.rightend.left = self.leftend
|
||||
self.hash[0] = self.leftend
|
||||
self.hash[-1] = self.rightend
|
||||
|
||||
def insert(self, left, he):
|
||||
he.left = left
|
||||
he.right = left.right
|
||||
left.right.left = he
|
||||
left.right = he
|
||||
|
||||
def delete(self, he):
|
||||
he.left.right = he.right
|
||||
he.right.left = he.left
|
||||
he.edge = Edge.DELETED
|
||||
|
||||
# Get entry from hash table, pruning any deleted nodes
|
||||
def gethash(self, b):
|
||||
if(b < 0 or b >= self.hashsize):
|
||||
return None
|
||||
he = self.hash[b]
|
||||
if he is None or he.edge is not Edge.DELETED:
|
||||
return he
|
||||
|
||||
# Hash table points to deleted half edge. Patch as necessary.
|
||||
self.hash[b] = None
|
||||
return None
|
||||
|
||||
def leftbnd(self, pt):
|
||||
# Use hash table to get close to desired halfedge
|
||||
bucket = int(((pt.x - self.xmin) / self.deltax * self.hashsize))
|
||||
|
||||
if(bucket < 0):
|
||||
bucket = 0
|
||||
|
||||
if(bucket >= self.hashsize):
|
||||
bucket = self.hashsize - 1
|
||||
|
||||
he = self.gethash(bucket)
|
||||
if(he is None):
|
||||
i = 1
|
||||
while True:
|
||||
he = self.gethash(bucket - i)
|
||||
if (he is not None):
|
||||
break
|
||||
he = self.gethash(bucket + i)
|
||||
if (he is not None):
|
||||
break
|
||||
i += 1
|
||||
|
||||
# Now search linear list of halfedges for the correct one
|
||||
if (he is self.leftend) or (he is not self.rightend and he.isPointRightOf(pt)):
|
||||
he = he.right
|
||||
while he is not self.rightend and he.isPointRightOf(pt):
|
||||
he = he.right
|
||||
he = he.left
|
||||
else:
|
||||
he = he.left
|
||||
while (he is not self.leftend and not he.isPointRightOf(pt)):
|
||||
he = he.left
|
||||
|
||||
# Update hash table and reference counts
|
||||
if(bucket > 0 and bucket < self.hashsize - 1):
|
||||
self.hash[bucket] = he
|
||||
return he
|
||||
|
||||
|
||||
class PriorityQueue(object):
|
||||
|
||||
def __init__(self, ymin, ymax, nsites):
|
||||
self.ymin = ymin
|
||||
self.deltay = ymax - ymin
|
||||
self.hashsize = int(4 * math.sqrt(nsites))
|
||||
self.count = 0
|
||||
self.minidx = 0
|
||||
self.hash = []
|
||||
for i in range(self.hashsize):
|
||||
self.hash.append(Halfedge())
|
||||
|
||||
def __len__(self):
|
||||
return self.count
|
||||
|
||||
def isEmpty(self):
|
||||
return self.count == 0
|
||||
|
||||
def insert(self, he, site, offset):
|
||||
he.vertex = site
|
||||
he.ystar = site.y + offset
|
||||
last = self.hash[self.getBucket(he)]
|
||||
next = last.qnext
|
||||
while((next is not None) and he > next):
|
||||
last = next
|
||||
next = last.qnext
|
||||
he.qnext = last.qnext
|
||||
last.qnext = he
|
||||
self.count += 1
|
||||
|
||||
def delete(self, he):
|
||||
if (he.vertex is not None):
|
||||
last = self.hash[self.getBucket(he)]
|
||||
while last.qnext is not he:
|
||||
last = last.qnext
|
||||
last.qnext = he.qnext
|
||||
self.count -= 1
|
||||
he.vertex = None
|
||||
|
||||
def getBucket(self, he):
|
||||
bucket = int(((he.ystar - self.ymin) / self.deltay) * self.hashsize)
|
||||
if bucket < 0:
|
||||
bucket = 0
|
||||
if bucket >= self.hashsize:
|
||||
bucket = self.hashsize - 1
|
||||
if bucket < self.minidx:
|
||||
self.minidx = bucket
|
||||
return bucket
|
||||
|
||||
def getMinPt(self):
|
||||
while(self.hash[self.minidx].qnext is None):
|
||||
self.minidx += 1
|
||||
he = self.hash[self.minidx].qnext
|
||||
x = he.vertex.x
|
||||
y = he.ystar
|
||||
return Site(x, y)
|
||||
|
||||
def popMinHalfedge(self):
|
||||
curr = self.hash[self.minidx].qnext
|
||||
self.hash[self.minidx].qnext = curr.qnext
|
||||
self.count -= 1
|
||||
return curr
|
||||
|
||||
|
||||
class SiteList(object):
|
||||
|
||||
def __init__(self, pointList):
|
||||
self.__sites = []
|
||||
self.__sitenum = 0
|
||||
|
||||
self.__xmin = min([pt.x for pt in pointList])
|
||||
self.__ymin = min([pt.y for pt in pointList])
|
||||
self.__xmax = max([pt.x for pt in pointList])
|
||||
self.__ymax = max([pt.y for pt in pointList])
|
||||
self.__extent = (self.__xmin, self.__xmax, self.__ymin, self.__ymax)
|
||||
|
||||
for i, pt in enumerate(pointList):
|
||||
self.__sites.append(Site(pt.x, pt.y, i))
|
||||
self.__sites.sort()
|
||||
|
||||
def setSiteNumber(self, site):
|
||||
site.sitenum = self.__sitenum
|
||||
self.__sitenum += 1
|
||||
|
||||
class Iterator(object):
|
||||
|
||||
def __init__(this, lst):
|
||||
this.generator = (s for s in lst)
|
||||
|
||||
def __iter__(this):
|
||||
return this
|
||||
|
||||
def next(this):
|
||||
try:
|
||||
# Note: Blender is Python 3.x so no need for 2.x checks
|
||||
return this.generator.__next__()
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
def iterator(self):
|
||||
return SiteList.Iterator(self.__sites)
|
||||
|
||||
def __iter__(self):
|
||||
return SiteList.Iterator(self.__sites)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__sites)
|
||||
|
||||
def _getxmin(self):
|
||||
return self.__xmin
|
||||
|
||||
def _getymin(self):
|
||||
return self.__ymin
|
||||
|
||||
def _getxmax(self):
|
||||
return self.__xmax
|
||||
|
||||
def _getymax(self):
|
||||
return self.__ymax
|
||||
|
||||
def _getextent(self):
|
||||
return self.__extent
|
||||
|
||||
xmin = property(_getxmin)
|
||||
ymin = property(_getymin)
|
||||
xmax = property(_getxmax)
|
||||
ymax = property(_getymax)
|
||||
extent = property(_getextent)
|
||||
|
||||
|
||||
def computeVoronoiDiagram(points, xBuff=0, yBuff=0, polygonsOutput=False,
|
||||
formatOutput=False, closePoly=True):
|
||||
"""
|
||||
Takes :
|
||||
- a list of point objects (which must have x and y fields).
|
||||
- x and y buffer values which are the expansion percentages of the bounding box
|
||||
rectangle including all input points.
|
||||
Returns :
|
||||
- With default options :
|
||||
A list of 2-tuples, representing the two points of each Voronoi diagram edge.
|
||||
Each point contains 2-tuples which are the x,y coordinates of point.
|
||||
if formatOutput is True, returns :
|
||||
- a list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices.
|
||||
- and a list of 2-tuples (v1, v2) representing edges of the Voronoi diagram.
|
||||
v1 and v2 are the indices of the vertices at the end of the edge.
|
||||
- If polygonsOutput option is True, returns :
|
||||
A dictionary of polygons, keys are the indices of the input points,
|
||||
values contains n-tuples representing the n points of each Voronoi diagram polygon.
|
||||
Each point contains 2-tuples which are the x,y coordinates of point.
|
||||
if formatOutput is True, returns :
|
||||
- A list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices.
|
||||
- and a dictionary of input points indices. Values contains n-tuples representing
|
||||
the n points of each Voronoi diagram polygon.
|
||||
Each tuple contains the vertex indices of the polygon vertices.
|
||||
- if closePoly is True then, in the list of points of a polygon, last point will be the same of first point
|
||||
"""
|
||||
siteList = SiteList(points)
|
||||
context = Context()
|
||||
voronoi(siteList, context)
|
||||
context.setClipBuffer(xBuff, yBuff)
|
||||
if not polygonsOutput:
|
||||
clipEdges = context.getClipEdges()
|
||||
if formatOutput:
|
||||
vertices, edgesIdx = formatEdgesOutput(clipEdges)
|
||||
return vertices, edgesIdx
|
||||
else:
|
||||
return clipEdges
|
||||
else:
|
||||
clipPolygons = context.getClipPolygons(closePoly)
|
||||
if formatOutput:
|
||||
vertices, polyIdx = formatPolygonsOutput(clipPolygons)
|
||||
return vertices, polyIdx
|
||||
else:
|
||||
return clipPolygons
|
||||
|
||||
|
||||
def formatEdgesOutput(edges):
|
||||
# get list of points
|
||||
pts = []
|
||||
for edge in edges:
|
||||
pts.extend(edge)
|
||||
# get unique values
|
||||
pts = set(pts) # unique values (tuples are hashable)
|
||||
# get dict {values:index}
|
||||
valuesIdxDict = dict(zip(pts, range(len(pts))))
|
||||
# get edges index reference
|
||||
edgesIdx = []
|
||||
for edge in edges:
|
||||
edgesIdx.append([valuesIdxDict[pt] for pt in edge])
|
||||
return list(pts), edgesIdx
|
||||
|
||||
|
||||
def formatPolygonsOutput(polygons):
|
||||
# get list of points
|
||||
pts = []
|
||||
for poly in polygons.values():
|
||||
pts.extend(poly)
|
||||
# get unique values
|
||||
pts = set(pts) # unique values (tuples are hashable)
|
||||
# get dict {values:index}
|
||||
valuesIdxDict = dict(zip(pts, range(len(pts))))
|
||||
# get polygons index reference
|
||||
polygonsIdx = {}
|
||||
for inPtsIdx, poly in polygons.items():
|
||||
polygonsIdx[inPtsIdx] = [valuesIdxDict[pt] for pt in poly]
|
||||
return list(pts), polygonsIdx
|
||||
|
||||
|
||||
def computeDelaunayTriangulation(points):
|
||||
""" Takes a list of point objects (which must have x and y fields).
|
||||
Returns a list of 3-tuples: the indices of the points that form a
|
||||
Delaunay triangle.
|
||||
"""
|
||||
siteList = SiteList(points)
|
||||
context = Context()
|
||||
context.triangulate = True
|
||||
voronoi(siteList, context)
|
||||
return context.triangles
|
|
@ -1,515 +0,0 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# Contributed to by:
|
||||
# meta-androcto, Bill Currie, Jorge Hernandez - Melenedez Jacob Morris, Oscurart #
|
||||
# Rebellion, Antonis Karvelas, Eleanor Howick, lijenstina, Daniel Schalla, Domlysz #
|
||||
# Unnikrishnan(kodemax), Florian Meyer, Omar ahmed, Brian Hinton (Nichod), liero #
|
||||
# Atom, Dannyboy, Mano-Wii, Kursad Karatas, teldredge, Phil Cote #
|
||||
|
||||
bl_info = {
|
||||
"name": "Add Advanced Object Panels",
|
||||
"author": "meta-androcto",
|
||||
"version": (1, 1, 5),
|
||||
"blender": (2, 77, 0),
|
||||
"description": "Individual Create Panel Activation List",
|
||||
"location": "Addons Preferences",
|
||||
"warning": "",
|
||||
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6"
|
||||
"/Py/Scripts/Object/Add_Advanced",
|
||||
"category": "Object"
|
||||
}
|
||||
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
AddonPreferences,
|
||||
PropertyGroup,
|
||||
)
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
BoolVectorProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
IntProperty,
|
||||
StringProperty,
|
||||
PointerProperty,
|
||||
)
|
||||
|
||||
sub_modules_names = (
|
||||
"drop_to_ground",
|
||||
"object_laplace_lightning",
|
||||
"object_mangle_tools",
|
||||
"unfold_transition",
|
||||
"delaunay_voronoi",
|
||||
"oscurart_constellation",
|
||||
)
|
||||
|
||||
|
||||
sub_modules = [__import__(__package__ + "." + submod, {}, {}, submod) for submod in sub_modules_names]
|
||||
sub_modules.sort(key=lambda mod: (mod.bl_info['category'], mod.bl_info['name']))
|
||||
|
||||
|
||||
# Add-ons Preferences
|
||||
def _get_pref_class(mod):
|
||||
import inspect
|
||||
|
||||
for obj in vars(mod).values():
|
||||
if inspect.isclass(obj) and issubclass(obj, PropertyGroup):
|
||||
if hasattr(obj, 'bl_idname') and obj.bl_idname == mod.__name__:
|
||||
return obj
|
||||
|
||||
|
||||
def get_addon_preferences(name=''):
|
||||
"""Acquisition and registration"""
|
||||
addons = bpy.context.preferences.addons
|
||||
if __name__ not in addons: # wm.read_factory_settings()
|
||||
return None
|
||||
addon_prefs = addons[__name__].preferences
|
||||
if name:
|
||||
if not hasattr(addon_prefs, name):
|
||||
for mod in sub_modules:
|
||||
if mod.__name__.split('.')[-1] == name:
|
||||
cls = _get_pref_class(mod)
|
||||
if cls:
|
||||
prop = PointerProperty(type=cls)
|
||||
setattr(AdvancedObjPreferences1, name, prop)
|
||||
bpy.utils.unregister_class(AdvancedObjPreferences1)
|
||||
bpy.utils.register_class(AdvancedObjPreferences1)
|
||||
return getattr(addon_prefs, name, None)
|
||||
else:
|
||||
return addon_prefs
|
||||
|
||||
|
||||
def register_submodule(mod):
|
||||
if not hasattr(mod, '__addon_enabled__'):
|
||||
mod.__addon_enabled__ = False
|
||||
if not mod.__addon_enabled__:
|
||||
mod.register()
|
||||
mod.__addon_enabled__ = True
|
||||
|
||||
|
||||
def unregister_submodule(mod):
|
||||
if mod.__addon_enabled__:
|
||||
mod.unregister()
|
||||
mod.__addon_enabled__ = False
|
||||
|
||||
prefs = get_addon_preferences()
|
||||
name = mod.__name__.split('.')[-1]
|
||||
if hasattr(AdvancedObjPreferences1, name):
|
||||
delattr(AdvancedObjPreferences1, name)
|
||||
if prefs:
|
||||
bpy.utils.unregister_class(AdvancedObjPreferences1)
|
||||
bpy.utils.register_class(AdvancedObjPreferences1)
|
||||
if name in prefs:
|
||||
del prefs[name]
|
||||
|
||||
|
||||
def enable_all_modules(self, context):
|
||||
for mod in sub_modules:
|
||||
mod_name = mod.__name__.split('.')[-1]
|
||||
setattr(self, 'use_' + mod_name, False)
|
||||
if not mod.__addon_enabled__:
|
||||
setattr(self, 'use_' + mod_name, True)
|
||||
mod.__addon_enabled__ = True
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def disable_all_modules(self, context):
|
||||
for mod in sub_modules:
|
||||
mod_name = mod.__name__.split('.')[-1]
|
||||
|
||||
if mod.__addon_enabled__:
|
||||
setattr(self, 'use_' + mod_name, False)
|
||||
mod.__addon_enabled__ = False
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class AdvancedObjPreferences1(AddonPreferences):
|
||||
bl_idname = __name__
|
||||
|
||||
enable_all: BoolProperty(
|
||||
name="Enable all",
|
||||
description="Enable all Advanced Objects' Panels",
|
||||
default=False,
|
||||
update=enable_all_modules
|
||||
)
|
||||
disable_all: BoolProperty(
|
||||
name="Disable all",
|
||||
description="Disable all Advanced Objects' Panels",
|
||||
default=False,
|
||||
update=disable_all_modules
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
split = layout.split(percentage=0.5, align=True)
|
||||
row = split.row()
|
||||
row.alignment = "LEFT"
|
||||
sub_box = row.box()
|
||||
sub_box.prop(self, "enable_all", emboss=False,
|
||||
icon="VISIBLE_IPO_ON", icon_only=True)
|
||||
row.label(text="Enable All")
|
||||
|
||||
row = split.row()
|
||||
row.alignment = "RIGHT"
|
||||
row.label(text="Disable All")
|
||||
sub_box = row.box()
|
||||
sub_box.prop(self, "disable_all", emboss=False,
|
||||
icon="VISIBLE_IPO_OFF", icon_only=True)
|
||||
|
||||
for mod in sub_modules:
|
||||
mod_name = mod.__name__.split('.')[-1]
|
||||
info = mod.bl_info
|
||||
column = layout.column()
|
||||
box = column.box()
|
||||
|
||||
# first stage
|
||||
expand = getattr(self, 'show_expanded_' + mod_name)
|
||||
icon = 'TRIA_DOWN' if expand else 'TRIA_RIGHT'
|
||||
col = box.column()
|
||||
row = col.row()
|
||||
sub = row.row()
|
||||
sub.context_pointer_set('addon_prefs', self)
|
||||
op = sub.operator('wm.context_toggle', text='', icon=icon,
|
||||
emboss=False)
|
||||
op.data_path = 'addon_prefs.show_expanded_' + mod_name
|
||||
sub.label(text='{}: {}'.format(info['category'], info['name']))
|
||||
sub = row.row()
|
||||
sub.alignment = 'RIGHT'
|
||||
if info.get('warning'):
|
||||
sub.label(text='', icon='ERROR')
|
||||
sub.prop(self, 'use_' + mod_name, text='')
|
||||
|
||||
# The second stage
|
||||
if expand:
|
||||
if info.get('description'):
|
||||
split = col.row().split(percentage=0.15)
|
||||
split.label(text='Description:')
|
||||
split.label(text=info['description'])
|
||||
if info.get('location'):
|
||||
split = col.row().split(percentage=0.15)
|
||||
split.label(text='Location:')
|
||||
split.label(text=info['location'])
|
||||
if info.get('author'):
|
||||
split = col.row().split(percentage=0.15)
|
||||
split.label(text='Author:')
|
||||
split.label(text=info['author'])
|
||||
if info.get('version'):
|
||||
split = col.row().split(percentage=0.15)
|
||||
split.label(text='Version:')
|
||||
split.label(text='.'.join(str(x) for x in info['version']),
|
||||
translate=False)
|
||||
if info.get('warning'):
|
||||
split = col.row().split(percentage=0.15)
|
||||
split.label(text='Warning:')
|
||||
split.label(text=' ' + info['warning'], icon='ERROR')
|
||||
|
||||
tot_row = int(bool(info.get('wiki_url')))
|
||||
if tot_row:
|
||||
split = col.row().split(percentage=0.15)
|
||||
split.label(text='Internet:')
|
||||
if info.get('wiki_url'):
|
||||
op = split.operator('wm.url_open',
|
||||
text='Documentation', icon='HELP')
|
||||
op.url = info.get('wiki_url')
|
||||
for i in range(4 - tot_row):
|
||||
split.separator()
|
||||
|
||||
# Details and settings
|
||||
if getattr(self, 'use_' + mod_name):
|
||||
prefs = get_addon_preferences(mod_name)
|
||||
|
||||
if prefs and hasattr(prefs, 'draw'):
|
||||
box = box.column()
|
||||
prefs.layout = box
|
||||
try:
|
||||
prefs.draw(context)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
box.label(text="Error (see console)", icon="ERROR")
|
||||
del prefs.layout
|
||||
|
||||
row = layout.row()
|
||||
row.label(text="End of Advanced Object Panels Activations",
|
||||
icon="FILE_PARENT")
|
||||
|
||||
|
||||
for mod in sub_modules:
|
||||
info = mod.bl_info
|
||||
mod_name = mod.__name__.split('.')[-1]
|
||||
|
||||
def gen_update(mod):
|
||||
def update(self, context):
|
||||
if getattr(self, 'use_' + mod.__name__.split('.')[-1]):
|
||||
if not mod.__addon_enabled__:
|
||||
register_submodule(mod)
|
||||
else:
|
||||
if mod.__addon_enabled__:
|
||||
unregister_submodule(mod)
|
||||
return update
|
||||
|
||||
prop = BoolProperty(
|
||||
name=info['name'],
|
||||
description=info.get('description', ''),
|
||||
update=gen_update(mod),
|
||||
)
|
||||
setattr(AdvancedObjPreferences1, 'use_' + mod_name, prop)
|
||||
prop = BoolProperty()
|
||||
setattr(AdvancedObjPreferences1, 'show_expanded_' + mod_name, prop)
|
||||
|
||||
|
||||
class AdvancedObjProperties1(PropertyGroup):
|
||||
|
||||
# object_laplace_lighting props
|
||||
ORIGIN: FloatVectorProperty(
|
||||
name="Origin charge"
|
||||
)
|
||||
GROUNDZ: IntProperty(
|
||||
name="Ground Z coordinate"
|
||||
)
|
||||
HORDER: IntProperty(
|
||||
name="Secondary paths orders",
|
||||
default=1
|
||||
)
|
||||
# object_laplace_lighting UI props
|
||||
TSTEPS: IntProperty(
|
||||
name="Iterations",
|
||||
default=350,
|
||||
description="Number of cells to create\n"
|
||||
"Will end early if hits ground plane or cloud"
|
||||
)
|
||||
GSCALE: FloatProperty(
|
||||
name="Grid unit size",
|
||||
default=0.12,
|
||||
description="scale of cells, .25 = 4 cells per blenderUnit"
|
||||
)
|
||||
BIGVAR: FloatProperty(
|
||||
name="Straightness",
|
||||
default=6.3,
|
||||
description="Straightness/branchiness of bolt, \n"
|
||||
"<2 is mush, >12 is staight line, 6.3 is good"
|
||||
)
|
||||
GROUNDBOOL: BoolProperty(
|
||||
name="Use Ground object",
|
||||
description="Use ground plane or not",
|
||||
default=True
|
||||
)
|
||||
GROUNDC: IntProperty(
|
||||
name="Ground charge",
|
||||
default=-250,
|
||||
description="Charge of the ground plane"
|
||||
)
|
||||
CLOUDBOOL: BoolProperty(
|
||||
name="Use Cloud object",
|
||||
default=False,
|
||||
description="Use cloud object - attracts and terminates like ground but\n"
|
||||
"any obj instead of z plane\n"
|
||||
"Can slow down loop if obj is large, overrides ground"
|
||||
)
|
||||
CLOUDC: IntProperty(
|
||||
name="Cloud charge",
|
||||
default=-1,
|
||||
description="Charge of a cell in cloud object\n"
|
||||
"(so total charge also depends on obj size)"
|
||||
)
|
||||
VMMESH: BoolProperty(
|
||||
name="Multi mesh",
|
||||
default=True,
|
||||
description="Output to multi-meshes for different materials on main/sec/side branches"
|
||||
)
|
||||
VSMESH: BoolProperty(
|
||||
name="Single mesh",
|
||||
default=False,
|
||||
description="Output to single mesh for using build modifier and particles for effects"
|
||||
)
|
||||
VCUBE: BoolProperty(
|
||||
name="Cubes",
|
||||
default=False,
|
||||
description="CTRL-J after run to JOIN\n"
|
||||
"Outputs a bunch of cube objects, mostly for testing"
|
||||
)
|
||||
VVOX: BoolProperty(
|
||||
name="Voxel (experimental)",
|
||||
default=False,
|
||||
description="Output to a voxel file to bpy.data.filepath\FSLGvoxels.raw\n"
|
||||
"(doesn't work well right now)"
|
||||
)
|
||||
IBOOL: BoolProperty(
|
||||
name="Use Insulator object",
|
||||
default=False,
|
||||
description="Use insulator mesh object to prevent growth of bolt in areas"
|
||||
)
|
||||
OOB: StringProperty(
|
||||
name="Select",
|
||||
default="",
|
||||
description="Origin of bolt, can be an Empty\n"
|
||||
"if object is a mesh will use all verts as charges")
|
||||
GOB: StringProperty(
|
||||
name="Select",
|
||||
default="",
|
||||
description="Object to use as ground plane, uses z coord only"
|
||||
)
|
||||
COB: StringProperty(
|
||||
name="Select",
|
||||
default="",
|
||||
description="Object to use as cloud, best to use a cube"
|
||||
)
|
||||
IOB: StringProperty(
|
||||
name="Select",
|
||||
default="",
|
||||
description="Object to use as insulator, 'voxelized'\n"
|
||||
"before generating bolt (can be slow)"
|
||||
)
|
||||
# object_mangle_tools properties
|
||||
mangle_constraint_vector = BoolVectorProperty(
|
||||
name="Mangle Constraint",
|
||||
default=(True, True, True),
|
||||
subtype='XYZ',
|
||||
description="Constrains Mangle Direction"
|
||||
)
|
||||
mangle_random_magnitude: IntProperty(
|
||||
name="Mangle Severity",
|
||||
default=5,
|
||||
min=1, max=30,
|
||||
description="Severity of mangling"
|
||||
)
|
||||
mangle_name: StringProperty(
|
||||
name="Shape Key Name",
|
||||
default="mangle",
|
||||
description="Name given for mangled shape keys"
|
||||
)
|
||||
# unfold_transition properties
|
||||
unfold_arm_name: StringProperty(
|
||||
default=""
|
||||
)
|
||||
unfold_modo: EnumProperty(
|
||||
name="",
|
||||
items=[("cursor", "3D Cursor", "Use the Distance to 3D Cursor"),
|
||||
("weight", "Weight Map", "Use a Painted Weight map"),
|
||||
("index", "Mesh Indices", "Use Faces and Vertices index")],
|
||||
description="How to Sort Bones for animation", default="cursor"
|
||||
)
|
||||
unfold_flip: BoolProperty(
|
||||
name="Flipping Faces",
|
||||
default=False,
|
||||
description="Rotate faces around the Center and skip Scaling - "
|
||||
"keep checked for both operators"
|
||||
)
|
||||
unfold_fold_duration: IntProperty(
|
||||
name="Total Time",
|
||||
min=5, soft_min=25,
|
||||
max=10000, soft_max=2500,
|
||||
default=200,
|
||||
description="Total animation length"
|
||||
)
|
||||
unfold_sca_time: IntProperty(
|
||||
name="Scale Time",
|
||||
min=1,
|
||||
max=5000, soft_max=500,
|
||||
default=10,
|
||||
description="Faces scaling time"
|
||||
)
|
||||
unfold_rot_time: IntProperty(
|
||||
name="Rotation Time",
|
||||
min=1, soft_min=5,
|
||||
max=5000, soft_max=500,
|
||||
default=15,
|
||||
description="Faces rotation time"
|
||||
)
|
||||
unfold_rot_max: IntProperty(
|
||||
name="Angle",
|
||||
min=-180,
|
||||
max=180,
|
||||
default=135,
|
||||
description="Faces rotation angle"
|
||||
)
|
||||
unfold_fold_noise: IntProperty(
|
||||
name="Noise",
|
||||
min=0,
|
||||
max=500, soft_max=50,
|
||||
default=0,
|
||||
description="Offset some faces animation"
|
||||
)
|
||||
unfold_bounce: FloatProperty(
|
||||
name="Bounce",
|
||||
min=0,
|
||||
max=10, soft_max=2.5,
|
||||
default=0,
|
||||
description="Add some bounce to rotation"
|
||||
)
|
||||
unfold_from_point: BoolProperty(
|
||||
name="Point",
|
||||
default=False,
|
||||
description="Scale faces from a Point instead of from an Edge"
|
||||
)
|
||||
unfold_wiggle_rot: BoolProperty(
|
||||
name="Wiggle",
|
||||
default=False,
|
||||
description="Use all Axis + Random Rotation instead of X Aligned"
|
||||
)
|
||||
# oscurart_constellation
|
||||
constellation_limit: FloatProperty(
|
||||
name="Initial Threshold",
|
||||
description="Edges will be created only if the distance\n"
|
||||
"between vertices is smaller than this value\n"
|
||||
"This is a starting value on Operator Invoke",
|
||||
default=2,
|
||||
min=0
|
||||
)
|
||||
|
||||
|
||||
# Class list
|
||||
classes = (
|
||||
AdvancedObjPreferences1,
|
||||
AdvancedObjProperties1,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
bpy.types.Scene.advanced_objects1 = PointerProperty(
|
||||
type=AdvancedObjProperties1
|
||||
)
|
||||
|
||||
prefs = get_addon_preferences()
|
||||
for mod in sub_modules:
|
||||
if not hasattr(mod, '__addon_enabled__'):
|
||||
mod.__addon_enabled__ = False
|
||||
name = mod.__name__.split('.')[-1]
|
||||
if getattr(prefs, 'use_' + name):
|
||||
register_submodule(mod)
|
||||
|
||||
|
||||
def unregister():
|
||||
for mod in sub_modules:
|
||||
if mod.__addon_enabled__:
|
||||
unregister_submodule(mod)
|
||||
del bpy.types.Scene.advanced_objects1
|
||||
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,344 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
bl_info = {
|
||||
"name": "Delaunay Voronoi",
|
||||
"description": "Points cloud Delaunay triangulation in 2.5D "
|
||||
"(suitable for terrain modelling) or Voronoi diagram in 2D",
|
||||
"author": "Domlysz, Oscurart",
|
||||
"version": (1, 3),
|
||||
"blender": (2, 70, 0),
|
||||
"location": "3D View > Toolshelf > Create > Delaunay Voronoi",
|
||||
"warning": "",
|
||||
"wiki_url": "https://github.com/domlysz/BlenderGIS/wiki",
|
||||
"category": "Add Mesh"
|
||||
}
|
||||
|
||||
import bpy
|
||||
from .DelaunayVoronoi import (
|
||||
computeVoronoiDiagram,
|
||||
computeDelaunayTriangulation,
|
||||
)
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
Panel,
|
||||
)
|
||||
from bpy.props import EnumProperty
|
||||
|
||||
try:
|
||||
from scipy.spatial import Delaunay
|
||||
import bmesh
|
||||
import numpy as np
|
||||
HAS_SCIPY = True
|
||||
except:
|
||||
HAS_SCIPY = False
|
||||
pass
|
||||
|
||||
|
||||
# Globals
|
||||
# set to True to enable debug_prints
|
||||
DEBUG = False
|
||||
|
||||
|
||||
def debug_prints(text=""):
|
||||
global DEBUG
|
||||
if DEBUG and text:
|
||||
print(text)
|
||||
|
||||
|
||||
class Point:
|
||||
def __init__(self, x, y, z):
|
||||
self.x, self.y, self.z = x, y, z
|
||||
|
||||
|
||||
def unique(L):
|
||||
"""Return a list of unhashable elements in s, but without duplicates.
|
||||
[[1, 2], [2, 3], [1, 2]] >>> [[1, 2], [2, 3]]"""
|
||||
# For unhashable objects, you can sort the sequence and
|
||||
# then scan from the end of the list, deleting duplicates as you go
|
||||
nDupli = 0
|
||||
nZcolinear = 0
|
||||
# sort() brings the equal elements together; then duplicates
|
||||
# are easy to weed out in a single pass
|
||||
L.sort()
|
||||
last = L[-1]
|
||||
for i in range(len(L) - 2, -1, -1):
|
||||
if last[:2] == L[i][:2]: # XY coordinates compararison
|
||||
if last[2] == L[i][2]: # Z coordinates compararison
|
||||
nDupli += 1 # duplicates vertices
|
||||
else: # Z colinear
|
||||
nZcolinear += 1
|
||||
del L[i]
|
||||
else:
|
||||
last = L[i]
|
||||
# list data type is mutable, input list will automatically update
|
||||
# and doesn't need to be returned
|
||||
return (nDupli, nZcolinear)
|
||||
|
||||
|
||||
def checkEqual(lst):
|
||||
return lst[1:] == lst[:-1]
|
||||
|
||||
|
||||
class ToolsPanelDelaunay(Panel):
|
||||
bl_category = "Create"
|
||||
bl_label = "Delaunay Voronoi"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context = "objectmode"
|
||||
bl_region_type = "TOOLS"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
box = layout.box()
|
||||
col = box.column(align=True)
|
||||
col.label(text="Point Cloud:")
|
||||
col.operator("delaunay.triangulation")
|
||||
col.operator("voronoi.tesselation")
|
||||
|
||||
|
||||
class OBJECT_OT_TriangulateButton(Operator):
|
||||
bl_idname = "delaunay.triangulation"
|
||||
bl_label = "Triangulation"
|
||||
bl_description = ("Terrain points cloud Delaunay triangulation in 2.5D\n"
|
||||
"Needs an existing Active Mesh Object")
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return (obj is not None and obj.type == "MESH")
|
||||
|
||||
def execute(self, context):
|
||||
# move the check into the poll
|
||||
obj = context.active_object
|
||||
|
||||
if HAS_SCIPY:
|
||||
# Use scipy when present (~18 x faster)
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
points_3D = [list(v.co) for v in bm.verts]
|
||||
points_2D = np.array([[v[0], v[1]] for v in points_3D])
|
||||
print("Triangulate " + str(len(points_3D)) + " points...")
|
||||
# Triangulate
|
||||
tri = Delaunay(points_2D)
|
||||
faces = tri.simplices.tolist()
|
||||
# Create new mesh structure
|
||||
print("Create mesh...")
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
mesh = bpy.data.meshes.new("TIN")
|
||||
mesh.from_pydata(points_3D, [], faces)
|
||||
mesh.update(calc_edges=True)
|
||||
my = bpy.data.objects.new("TIN", mesh)
|
||||
context.collection.objects.link(my)
|
||||
my.matrix_world = obj.matrix_world.copy()
|
||||
obj.select_set(False)
|
||||
my.select_set(True)
|
||||
context.view_layer.objects.active = my
|
||||
self.report({'INFO'}, "Mesh created (" + str(len(faces)) + " triangles)")
|
||||
print("Total :%s faces %s verts" % (len(faces), len(points_3D)))
|
||||
return {'FINISHED'}
|
||||
|
||||
# Get points coordinates
|
||||
r = obj.rotation_euler
|
||||
s = obj.scale
|
||||
mesh = obj.data
|
||||
vertsPts = [vertex.co for vertex in mesh.vertices]
|
||||
|
||||
# Remove duplicate
|
||||
verts = [[vert.x, vert.y, vert.z] for vert in vertsPts]
|
||||
nDupli, nZcolinear = unique(verts)
|
||||
nVerts = len(verts)
|
||||
|
||||
debug_prints(text=str(nDupli) + " duplicate points ignored")
|
||||
debug_prints(str(nZcolinear) + " z colinear points excluded")
|
||||
|
||||
if nVerts < 3:
|
||||
self.report({"WARNING"},
|
||||
"Not enough points to continue. Operation Cancelled")
|
||||
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Check colinear
|
||||
xValues = [pt[0] for pt in verts]
|
||||
yValues = [pt[1] for pt in verts]
|
||||
|
||||
if checkEqual(xValues) or checkEqual(yValues):
|
||||
self.report({'ERROR'}, "Points are colinear")
|
||||
return {'FINISHED'}
|
||||
|
||||
# Triangulate
|
||||
debug_prints(text="Triangulate " + str(nVerts) + " points...")
|
||||
|
||||
vertsPts = [Point(vert[0], vert[1], vert[2]) for vert in verts]
|
||||
triangles = computeDelaunayTriangulation(vertsPts)
|
||||
# reverse point order --> if all triangles are specified anticlockwise then all faces up
|
||||
triangles = [tuple(reversed(tri)) for tri in triangles]
|
||||
|
||||
debug_prints(text=str(len(triangles)) + " triangles")
|
||||
|
||||
# Create new mesh structure
|
||||
debug_prints(text="Create mesh...")
|
||||
tinMesh = bpy.data.meshes.new("TIN") # create a new mesh
|
||||
tinMesh.from_pydata(verts, [], triangles) # Fill the mesh with triangles
|
||||
tinMesh.update(calc_edges=True) # Update mesh with new data
|
||||
|
||||
# Create an object with that mesh
|
||||
tinObj = bpy.data.objects.new("TIN", tinMesh)
|
||||
|
||||
# Place object
|
||||
tinObj.location = obj.location.copy()
|
||||
tinObj.rotation_euler = r
|
||||
tinObj.scale = s
|
||||
|
||||
# Update scene
|
||||
bpy.context.collection.objects.link(tinObj) # Link object to collection
|
||||
bpy.context.view_layer.objects.active = tinObj
|
||||
tinObj.select_set(True)
|
||||
obj.select_set(False)
|
||||
|
||||
self.report({"INFO"},
|
||||
"Mesh created (" + str(len(triangles)) + " triangles)")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class OBJECT_OT_VoronoiButton(Operator):
|
||||
bl_idname = "voronoi.tesselation"
|
||||
bl_label = "Diagram"
|
||||
bl_description = ("Points cloud Voronoi diagram in 2D\n"
|
||||
"Needs an existing Active Mesh Object")
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
meshType: EnumProperty(
|
||||
items=[('Edges', "Edges", "Edges Only - do not fill Faces"),
|
||||
('Faces', "Faces", "Fill Faces in the new Object")],
|
||||
name="Mesh type",
|
||||
description="Type of geometry to generate"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return (obj is not None and obj.type == "MESH")
|
||||
|
||||
def execute(self, context):
|
||||
# move the check into the poll
|
||||
obj = context.active_object
|
||||
|
||||
# Get points coordinates
|
||||
r = obj.rotation_euler
|
||||
s = obj.scale
|
||||
mesh = obj.data
|
||||
vertsPts = [vertex.co for vertex in mesh.vertices]
|
||||
|
||||
# Remove duplicate
|
||||
verts = [[vert.x, vert.y, vert.z] for vert in vertsPts]
|
||||
nDupli, nZcolinear = unique(verts)
|
||||
nVerts = len(verts)
|
||||
|
||||
debug_prints(text=str(nDupli) + " duplicates points ignored")
|
||||
debug_prints(text=str(nZcolinear) + " z colinear points excluded")
|
||||
|
||||
if nVerts < 3:
|
||||
self.report({"WARNING"},
|
||||
"Not enough points to continue. Operation Cancelled")
|
||||
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Check colinear
|
||||
xValues = [pt[0] for pt in verts]
|
||||
yValues = [pt[1] for pt in verts]
|
||||
|
||||
if checkEqual(xValues) or checkEqual(yValues):
|
||||
self.report({"WARNING"},
|
||||
"Points are colinear. Operation Cancelled")
|
||||
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Create diagram
|
||||
debug_prints(text="Tesselation... (" + str(nVerts) + " points)")
|
||||
|
||||
xbuff, ybuff = 5, 5
|
||||
zPosition = 0
|
||||
vertsPts = [Point(vert[0], vert[1], vert[2]) for vert in verts]
|
||||
|
||||
if self.meshType == "Edges":
|
||||
pts, edgesIdx = computeVoronoiDiagram(
|
||||
vertsPts, xbuff, ybuff,
|
||||
polygonsOutput=False, formatOutput=True
|
||||
)
|
||||
else:
|
||||
pts, polyIdx = computeVoronoiDiagram(
|
||||
vertsPts, xbuff, ybuff, polygonsOutput=True,
|
||||
formatOutput=True, closePoly=False
|
||||
)
|
||||
|
||||
pts = [[pt[0], pt[1], zPosition] for pt in pts]
|
||||
|
||||
# Create new mesh structure
|
||||
voronoiDiagram = bpy.data.meshes.new("VoronoiDiagram") # create a new mesh
|
||||
|
||||
if self.meshType == "Edges":
|
||||
# Fill the mesh with triangles
|
||||
voronoiDiagram.from_pydata(pts, edgesIdx, [])
|
||||
else:
|
||||
# Fill the mesh with triangles
|
||||
voronoiDiagram.from_pydata(pts, [], list(polyIdx.values()))
|
||||
|
||||
voronoiDiagram.update(calc_edges=True) # Update mesh with new data
|
||||
# create an object with that mesh
|
||||
voronoiObj = bpy.data.objects.new("VoronoiDiagram", voronoiDiagram)
|
||||
# place object
|
||||
voronoiObj.location = obj.location.copy()
|
||||
voronoiObj.rotation_euler = r
|
||||
voronoiObj.scale = s
|
||||
|
||||
# update scene
|
||||
bpy.context.collection.objects.link(voronoiObj) # Link object to collection
|
||||
bpy.context.view_layer.objects.active = voronoiObj
|
||||
voronoiObj.select_set(True)
|
||||
obj.select_set(False)
|
||||
|
||||
# Report
|
||||
if self.meshType == "Edges":
|
||||
self.report({"INFO"}, "Mesh created (" + str(len(edgesIdx)) + " edges)")
|
||||
else:
|
||||
self.report({"INFO"}, "Mesh created (" + str(len(polyIdx)) + " polygons)")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Register
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_VoronoiButton)
|
||||
bpy.utils.register_class(OBJECT_OT_TriangulateButton)
|
||||
bpy.utils.register_class(ToolsPanelDelaunay)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_VoronoiButton)
|
||||
bpy.utils.unregister_class(OBJECT_OT_TriangulateButton)
|
||||
bpy.utils.unregister_class(ToolsPanelDelaunay)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,378 +0,0 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
bl_info = {
|
||||
"name": "Drop to Ground1",
|
||||
"author": "Unnikrishnan(kodemax), Florian Meyer(testscreenings)",
|
||||
"blender": (2, 71, 0),
|
||||
"location": "3D View > Toolshelf > Create > Drop To Ground",
|
||||
"description": "Drop selected objects on active object",
|
||||
"warning": "",
|
||||
"category": "Object"}
|
||||
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
from mathutils import (
|
||||
Vector,
|
||||
Matrix,
|
||||
)
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
Panel,
|
||||
)
|
||||
from bpy.props import BoolProperty
|
||||
|
||||
|
||||
def test_ground_object(ground):
|
||||
if ground.type in {'MESH', 'FONT', 'META', 'CURVE', 'SURFACE'}:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_align_matrix(location, normal):
|
||||
up = Vector((0, 0, 1))
|
||||
angle = normal.angle(up)
|
||||
axis = up.cross(normal)
|
||||
mat_rot = Matrix.Rotation(angle, 4, axis)
|
||||
mat_loc = Matrix.Translation(location)
|
||||
mat_align = mat_rot * mat_loc
|
||||
return mat_align
|
||||
|
||||
|
||||
def transform_ground_to_world(sc, ground):
|
||||
tmpMesh = ground.to_mesh(sc, True, 'PREVIEW')
|
||||
tmpMesh.transform(ground.matrix_world)
|
||||
tmp_ground = bpy.data.objects.new('tmpGround', tmpMesh)
|
||||
sc.objects.link(tmp_ground)
|
||||
sc.update()
|
||||
|
||||
return tmp_ground
|
||||
|
||||
|
||||
def get_lowest_world_co_from_mesh(ob, mat_parent=None):
|
||||
bme = bmesh.new()
|
||||
bme.from_mesh(ob.data)
|
||||
mat_to_world = ob.matrix_world.copy()
|
||||
if mat_parent:
|
||||
mat_to_world = mat_parent * mat_to_world
|
||||
lowest = None
|
||||
for v in bme.verts:
|
||||
if not lowest:
|
||||
lowest = v
|
||||
if (mat_to_world * v.co).z < (mat_to_world * lowest.co).z:
|
||||
lowest = v
|
||||
lowest_co = mat_to_world * lowest.co
|
||||
bme.free()
|
||||
|
||||
return lowest_co
|
||||
|
||||
|
||||
def get_lowest_world_co(context, ob, mat_parent=None):
|
||||
if ob.type == 'MESH':
|
||||
return get_lowest_world_co_from_mesh(ob)
|
||||
|
||||
elif ob.type == 'EMPTY' and ob.instance_type == 'COLLECTION':
|
||||
if not ob.instance_collection:
|
||||
return None
|
||||
|
||||
else:
|
||||
lowest_co = None
|
||||
for ob_l in ob.instance_collection.objects:
|
||||
if ob_l.type == 'MESH':
|
||||
lowest_ob_l = get_lowest_world_co_from_mesh(ob_l, ob.matrix_world)
|
||||
if not lowest_co:
|
||||
lowest_co = lowest_ob_l
|
||||
if lowest_ob_l.z < lowest_co.z:
|
||||
lowest_co = lowest_ob_l
|
||||
|
||||
return lowest_co
|
||||
|
||||
|
||||
def drop_objectsall(self, context):
|
||||
ground = context.active_object
|
||||
name = ground.name
|
||||
|
||||
for obs in bpy.context.scene.objects:
|
||||
obs.select_set(True)
|
||||
if obs.name == name:
|
||||
obs.select_set(False)
|
||||
|
||||
obs2 = context.selected_objects
|
||||
|
||||
tmp_ground = transform_ground_to_world(context.scene, ground)
|
||||
down = Vector((0, 0, -10000))
|
||||
|
||||
for ob in obs2:
|
||||
if self.use_origin:
|
||||
lowest_world_co = ob.location
|
||||
else:
|
||||
lowest_world_co = get_lowest_world_co(context, ob)
|
||||
|
||||
if not lowest_world_co:
|
||||
message = "Object {} is of type {} works only with Use Center option " \
|
||||
"checked".format(ob.name, ob.type)
|
||||
self.reported.append(message)
|
||||
continue
|
||||
is_hit, hit_location, hit_normal, hit_index = tmp_ground.ray_cast(lowest_world_co, down)
|
||||
|
||||
if not is_hit:
|
||||
message = ob.name + " did not hit the Ground"
|
||||
self.reported.append(message)
|
||||
continue
|
||||
|
||||
# simple drop down
|
||||
to_ground_vec = hit_location - lowest_world_co
|
||||
ob.location += to_ground_vec
|
||||
|
||||
# drop with align to hit normal
|
||||
if self.align:
|
||||
to_center_vec = ob.location - hit_location # vec: hit_loc to origin
|
||||
# rotate object to align with face normal
|
||||
mat_normal = get_align_matrix(hit_location, hit_normal)
|
||||
rot_euler = mat_normal.to_euler()
|
||||
mat_ob_tmp = ob.matrix_world.copy().to_3x3()
|
||||
mat_ob_tmp.rotate(rot_euler)
|
||||
mat_ob_tmp = mat_ob_tmp.to_4x4()
|
||||
ob.matrix_world = mat_ob_tmp
|
||||
# move_object to hit_location
|
||||
ob.location = hit_location
|
||||
# move object above surface again
|
||||
to_center_vec.rotate(rot_euler)
|
||||
ob.location += to_center_vec
|
||||
|
||||
# cleanup
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
tmp_ground.select_set(True)
|
||||
bpy.ops.object.delete('EXEC_DEFAULT')
|
||||
for ob in obs2:
|
||||
ob.select_set(True)
|
||||
ground.select_set(True)
|
||||
|
||||
|
||||
def drop_objects(self, context):
|
||||
ground = context.active_object
|
||||
|
||||
obs = context.selected_objects
|
||||
if ground in obs:
|
||||
obs.remove(ground)
|
||||
|
||||
tmp_ground = transform_ground_to_world(context.scene, ground)
|
||||
down = Vector((0, 0, -10000))
|
||||
|
||||
for ob in obs:
|
||||
if self.use_origin:
|
||||
lowest_world_co = ob.location
|
||||
else:
|
||||
lowest_world_co = get_lowest_world_co(context, ob)
|
||||
|
||||
if not lowest_world_co:
|
||||
message = "Object {} is of type {} works only with Use Center option " \
|
||||
"checked".format(ob.name, ob.type)
|
||||
self.reported.append(message)
|
||||
continue
|
||||
|
||||
is_hit, hit_location, hit_normal, hit_index = tmp_ground.ray_cast(lowest_world_co, down)
|
||||
if not is_hit:
|
||||
message = ob.name + " did not hit the Active Object"
|
||||
self.reported.append(message)
|
||||
continue
|
||||
|
||||
# simple drop down
|
||||
to_ground_vec = hit_location - lowest_world_co
|
||||
ob.location += to_ground_vec
|
||||
|
||||
# drop with align to hit normal
|
||||
if self.align:
|
||||
to_center_vec = ob.location - hit_location # vec: hit_loc to origin
|
||||
# rotate object to align with face normal
|
||||
mat_normal = get_align_matrix(hit_location, hit_normal)
|
||||
rot_euler = mat_normal.to_euler()
|
||||
mat_ob_tmp = ob.matrix_world.copy().to_3x3()
|
||||
mat_ob_tmp.rotate(rot_euler)
|
||||
mat_ob_tmp = mat_ob_tmp.to_4x4()
|
||||
ob.matrix_world = mat_ob_tmp
|
||||
# move_object to hit_location
|
||||
ob.location = hit_location
|
||||
# move object above surface again
|
||||
to_center_vec.rotate(rot_euler)
|
||||
ob.location += to_center_vec
|
||||
|
||||
# cleanup
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
tmp_ground.select_set(True)
|
||||
bpy.ops.object.delete('EXEC_DEFAULT')
|
||||
for ob in obs:
|
||||
ob.select_set(True)
|
||||
ground.select_set(True)
|
||||
|
||||
|
||||
# define base dummy class for inheritance
|
||||
class DropBaseAtributes:
|
||||
align: BoolProperty(
|
||||
name="Align to ground",
|
||||
description="Aligns the objects' rotation to the ground",
|
||||
default=True)
|
||||
use_origin: BoolProperty(
|
||||
name="Use Origins",
|
||||
description="Drop to objects' origins\n"
|
||||
"Use this option for dropping all types of Objects",
|
||||
default=False)
|
||||
|
||||
|
||||
class OBJECT_OT_drop_to_ground(Operator, DropBaseAtributes):
|
||||
bl_idname = "object.drop_on_active"
|
||||
bl_label = "Drop to Ground"
|
||||
bl_description = ("Drop selected objects on the Active object\n"
|
||||
"Active Object has to be of following the types:\n"
|
||||
"Mesh, Font, Metaball, Curve, Surface")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
reported = []
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
act_obj = context.active_object
|
||||
return (len(context.selected_objects) >= 2 and
|
||||
act_obj and test_ground_object(act_obj))
|
||||
|
||||
def execute(self, context):
|
||||
drop_objects(self, context)
|
||||
|
||||
if self.reported:
|
||||
self.report({"INFO"},
|
||||
"Some objects could not be dropped (See the Console for more Info)")
|
||||
report_items = " \n".join(self.reported)
|
||||
print("\n[Drop to Ground Report]\n{}\n".format(report_items))
|
||||
|
||||
self.reported[:] = []
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class OBJECT_OT_drop_all_ground(Operator, DropBaseAtributes):
|
||||
bl_idname = "object.drop_all_active"
|
||||
bl_label = "Drop All to Ground (Active Object)"
|
||||
bl_description = ("Drop all other objects onto Active Object\n"
|
||||
"Active Object has to be of following the types:\n"
|
||||
"Mesh, Font, Metaball, Curve, Surface")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
reported = []
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
act_obj = context.active_object
|
||||
return act_obj and test_ground_object(act_obj)
|
||||
|
||||
def execute(self, context):
|
||||
drop_objectsall(self, context)
|
||||
|
||||
if self.reported:
|
||||
self.report({"INFO"},
|
||||
"Some objects could not be dropped (See the Console for more Info)")
|
||||
report_items = " \n".join(self.reported)
|
||||
print("\n[Drop All to Ground Report]\n{}\n".format(report_items))
|
||||
|
||||
self.reported[:] = []
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class Drop_help(Operator):
|
||||
bl_idname = "help.drop"
|
||||
bl_label = "Drop to Ground Help"
|
||||
bl_description = "Clik for some information about Drop to Ground"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
is_all: BoolProperty(
|
||||
default=True,
|
||||
options={"HIDDEN"}
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.label(text="General Info:")
|
||||
layout.label(text="The Active Object has to be of a Mesh, Font,")
|
||||
layout.label(text="Metaball, Curve or Surface type and")
|
||||
layout.label(text="be at the lowest Z location")
|
||||
layout.label(text="The option Use Origins must be enabled to drop")
|
||||
layout.label(text="objects that are not of a Mesh or DupliGroup type")
|
||||
layout.label(text="The Active Object has to be big enough to catch them")
|
||||
layout.label(text="To check that, use the Orthographic Top View")
|
||||
layout.separator()
|
||||
|
||||
layout.label(text="To use:")
|
||||
|
||||
if self.is_all is False:
|
||||
layout.label(text="Select objects to drop")
|
||||
layout.label(text="Then Shift Select the object to be the ground")
|
||||
layout.label(text="Drops Selected Object to the Active one")
|
||||
else:
|
||||
layout.label(text="Select the ground Mesh and press Drop all")
|
||||
layout.label(text="The unselected Objects will be moved straight")
|
||||
layout.label(text="down the Z axis, so they have to be above")
|
||||
layout.label(text="the Selected / Active one to fall")
|
||||
|
||||
def execute(self, context):
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_popup(self, width=300)
|
||||
|
||||
|
||||
class Drop_Operator_Panel(Panel):
|
||||
bl_label = "Drop To Ground"
|
||||
bl_region_type = "TOOLS"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
bl_context = "objectmode"
|
||||
bl_category = "Create"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
row = layout.split(percentage=0.8, align=True)
|
||||
row.operator(OBJECT_OT_drop_to_ground.bl_idname,
|
||||
text="Drop Selected")
|
||||
row.operator("help.drop", text="", icon="LAYER_USED").is_all = False
|
||||
|
||||
row = layout.split(percentage=0.8, align=True)
|
||||
row.operator(OBJECT_OT_drop_all_ground.bl_idname,
|
||||
text="Drop All")
|
||||
row.operator("help.drop", text="", icon="LAYER_USED").is_all = True
|
||||
|
||||
|
||||
# Register
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_drop_all_ground)
|
||||
bpy.utils.register_class(OBJECT_OT_drop_to_ground)
|
||||
bpy.utils.register_class(Drop_Operator_Panel)
|
||||
bpy.utils.register_class(Drop_help)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_drop_all_ground)
|
||||
bpy.utils.unregister_class(OBJECT_OT_drop_to_ground)
|
||||
bpy.utils.unregister_class(Drop_Operator_Panel)
|
||||
bpy.utils.unregister_class(Drop_help)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
File diff suppressed because it is too large
Load Diff
|
@ -1,208 +0,0 @@
|
|||
# mangle_tools.py (c) 2011 Phil Cote (cotejrp1)
|
||||
|
||||
# ###### BEGIN GPL LICENSE BLOCK ######
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ###### END GPL LICENCE BLOCK ######
|
||||
|
||||
# Note: properties are moved into __init__
|
||||
|
||||
bl_info = {
|
||||
"name": "Mangle Tools",
|
||||
"author": "Phil Cote",
|
||||
"blender": (2, 71, 0),
|
||||
"location": "3D View > Toolshelf > Create > Mangle Tools",
|
||||
"description": "Set of tools to mangle curves, meshes, and shape keys",
|
||||
"warning": "",
|
||||
"wiki_url": "",
|
||||
"category": "Object"}
|
||||
|
||||
|
||||
import bpy
|
||||
import random
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
Panel,
|
||||
)
|
||||
import time
|
||||
from math import pi
|
||||
import bmesh
|
||||
|
||||
|
||||
def move_coordinate(context, co, is_curve=False):
|
||||
advanced_objects = context.scene.advanced_objects1
|
||||
xyz_const = advanced_objects.mangle_constraint_vector
|
||||
random.seed(time.time())
|
||||
multiplier = 1
|
||||
|
||||
# For curves, we base the multiplier on the circumference formula.
|
||||
# This helps make curve changes more noticeable.
|
||||
if is_curve:
|
||||
multiplier = 2 * pi
|
||||
random_mag = advanced_objects.mangle_random_magnitude
|
||||
if xyz_const[0]:
|
||||
co.x += .01 * random.randrange(-random_mag, random_mag) * multiplier
|
||||
if xyz_const[1]:
|
||||
co.y += .01 * random.randrange(-random_mag, random_mag) * multiplier
|
||||
if xyz_const[2]:
|
||||
co.z += .01 * random.randrange(-random_mag, random_mag) * multiplier
|
||||
|
||||
|
||||
class MeshManglerOperator(Operator):
|
||||
bl_idname = "ba.mesh_mangler"
|
||||
bl_label = "Mangle Mesh"
|
||||
bl_description = ("Push vertices on the selected object around in random\n"
|
||||
"directions to create a crumpled look")
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
ob = context.active_object
|
||||
return ob is not None and ob.type == 'MESH'
|
||||
|
||||
def execute(self, context):
|
||||
mesh = context.active_object.data
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
verts = bm.verts
|
||||
advanced_objects = context.scene.advanced_objects1
|
||||
randomMag = advanced_objects.mangle_random_magnitude
|
||||
random.seed(time.time())
|
||||
|
||||
if mesh.shape_keys is not None:
|
||||
self.report({'INFO'},
|
||||
"Cannot mangle mesh: Shape keys present. Operation Cancelled")
|
||||
return {'CANCELLED'}
|
||||
|
||||
for vert in verts:
|
||||
xVal = .01 * random.randrange(-randomMag, randomMag)
|
||||
yVal = .01 * random.randrange(-randomMag, randomMag)
|
||||
zVal = .01 * random.randrange(-randomMag, randomMag)
|
||||
|
||||
vert.co.x = vert.co.x + xVal
|
||||
vert.co.y = vert.co.y + yVal
|
||||
vert.co.z = vert.co.z + zVal
|
||||
|
||||
del verts
|
||||
|
||||
bm.to_mesh(mesh)
|
||||
mesh.update()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class AnimanglerOperator(Operator):
|
||||
bl_idname = "ba.ani_mangler"
|
||||
bl_label = "Mangle Shape Key"
|
||||
bl_description = ("Make a shape key and pushes the verts around on it\n"
|
||||
"to set up for random pulsating animation")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
ob = context.active_object
|
||||
return ob is not None and ob.type in ['MESH', 'CURVE']
|
||||
|
||||
def execute(self, context):
|
||||
scn = context.scene.advanced_objects1
|
||||
mangleName = scn.mangle_name
|
||||
ob = context.object
|
||||
shapeKey = ob.shape_key_add(name=mangleName)
|
||||
verts = shapeKey.data
|
||||
|
||||
for vert in verts:
|
||||
move_coordinate(context, vert.co, is_curve=ob.type == 'CURVE')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class CurveManglerOp(Operator):
|
||||
bl_idname = "ba.curve_mangler"
|
||||
bl_label = "Mangle Curve"
|
||||
bl_description = "Mangle a curve to the degree the user specifies"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
ob = context.active_object
|
||||
return ob is not None and ob.type == "CURVE"
|
||||
|
||||
def execute(self, context):
|
||||
ob = context.active_object
|
||||
if ob.data.shape_keys is not None:
|
||||
self.report({'INFO'},
|
||||
"Cannot mangle curve. Shape keys present. Operation Cancelled")
|
||||
return {'CANCELLED'}
|
||||
|
||||
splines = context.object.data.splines
|
||||
|
||||
for spline in splines:
|
||||
if spline.type == 'BEZIER':
|
||||
points = spline.bezier_points
|
||||
elif spline.type in {'POLY', 'NURBS'}:
|
||||
points = spline.points
|
||||
|
||||
for point in points:
|
||||
move_coordinate(context, point.co, is_curve=True)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MangleToolsPanel(Panel):
|
||||
bl_label = "Mangle Tools"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context = "objectmode"
|
||||
bl_region_type = "TOOLS"
|
||||
bl_category = "Create"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw(self, context):
|
||||
scn = context.scene.advanced_objects1
|
||||
obj = context.object
|
||||
|
||||
if obj and obj.type in ['MESH']:
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(scn, "mangle_constraint_vector", toggle=True)
|
||||
|
||||
col = layout.column()
|
||||
col.prop(scn, "mangle_random_magnitude")
|
||||
col.operator("ba.mesh_mangler")
|
||||
col.separator()
|
||||
|
||||
col.prop(scn, "mangle_name")
|
||||
col.operator("ba.ani_mangler")
|
||||
else:
|
||||
layout = self.layout
|
||||
layout.label(text="Please select a Mesh Object", icon="INFO")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(AnimanglerOperator)
|
||||
bpy.utils.register_class(MeshManglerOperator)
|
||||
bpy.utils.register_class(CurveManglerOp)
|
||||
bpy.utils.register_class(MangleToolsPanel)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(AnimanglerOperator)
|
||||
bpy.utils.unregister_class(MeshManglerOperator)
|
||||
bpy.utils.unregister_class(MangleToolsPanel)
|
||||
bpy.utils.unregister_class(CurveManglerOp)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,145 +0,0 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
bl_info = {
|
||||
"name": "Constellation",
|
||||
"author": "Oscurart",
|
||||
"blender": (2, 67, 0),
|
||||
"location": "3D View > Toolshelf > Create > Constellation",
|
||||
"description": "Create a new Mesh From Selected",
|
||||
"warning": "",
|
||||
"wiki_url": "",
|
||||
"category": "Add Mesh"}
|
||||
|
||||
# Note the setting is moved to __init__ search for
|
||||
# the adv_obj and advanced_objects patterns
|
||||
|
||||
import bpy
|
||||
from bpy.props import FloatProperty
|
||||
from math import sqrt
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
Panel,
|
||||
)
|
||||
|
||||
|
||||
def VertDis(a, b):
|
||||
dst = sqrt(pow(a.co.x - b.co.x, 2) +
|
||||
pow(a.co.y - b.co.y, 2) +
|
||||
pow(a.co.z - b.co.z, 2))
|
||||
return(dst)
|
||||
|
||||
|
||||
def OscConstellation(limit):
|
||||
actobj = bpy.context.object
|
||||
vertlist = []
|
||||
edgelist = []
|
||||
edgei = 0
|
||||
|
||||
for ind, verta in enumerate(actobj.data.vertices[:]):
|
||||
for vertb in actobj.data.vertices[ind:]:
|
||||
if VertDis(verta, vertb) <= limit:
|
||||
vertlist.append(verta.co[:])
|
||||
vertlist.append(vertb.co[:])
|
||||
edgelist.append((edgei, edgei + 1))
|
||||
edgei += 2
|
||||
|
||||
mesh = bpy.data.meshes.new("rsdata")
|
||||
obj = bpy.data.objects.new("rsObject", mesh)
|
||||
bpy.context.collection.objects.link(obj)
|
||||
mesh.from_pydata(vertlist, edgelist, [])
|
||||
|
||||
|
||||
class Oscurart_Constellation(Operator):
|
||||
bl_idname = "mesh.constellation"
|
||||
bl_label = "Constellation"
|
||||
bl_description = ("Create a Constellation Mesh - Cloud of Vertices\n"
|
||||
"Note: can produce a lot of geometry\n"
|
||||
"Needs an existing Active Mesh Object")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
limit: FloatProperty(
|
||||
name="Threshold",
|
||||
description="Edges will be created only if the distance\n"
|
||||
"between vertices is smaller than this value",
|
||||
default=2,
|
||||
min=0
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return (obj and obj.type == "MESH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
adv_obj = context.scene.advanced_objects1
|
||||
self.limit = adv_obj.constellation_limit
|
||||
|
||||
return self.execute(context)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.prop(self, "limit")
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
OscConstellation(self.limit)
|
||||
except Exception as e:
|
||||
print("\n[Add Advanced Objects]\nOperator: mesh.constellation\n{}".format(e))
|
||||
|
||||
self.report({"WARNING"},
|
||||
"Constellation Operation could not be Completed (See Console for more Info)")
|
||||
|
||||
return {"CANCELLED"}
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class Constellation_Operator_Panel(Panel):
|
||||
bl_label = "Constellation"
|
||||
bl_region_type = "TOOLS"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
bl_context = "objectmode"
|
||||
bl_category = "Create"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
adv_obj = context.scene.advanced_objects1
|
||||
|
||||
box = layout.box()
|
||||
col = box.column(align=True)
|
||||
col.label(text="Constellation:")
|
||||
col.operator("mesh.constellation", text="Cross Section")
|
||||
col.prop(adv_obj, "constellation_limit")
|
||||
|
||||
|
||||
# Register
|
||||
def register():
|
||||
bpy.utils.register_class(Oscurart_Constellation)
|
||||
bpy.utils.register_class(Constellation_Operator_Panel)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(Oscurart_Constellation)
|
||||
bpy.utils.unregister_class(Constellation_Operator_Panel)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,348 +0,0 @@
|
|||
# gpl: authors Liero, Atom
|
||||
|
||||
bl_info = {
|
||||
"name": "Unfold transition",
|
||||
"author": "Liero, Atom",
|
||||
"location": "3D View > Toolshelf > Create > Unfold Transition",
|
||||
"description": "Simple unfold transition / animation, will "
|
||||
"separate faces and set up an armature",
|
||||
"category": "Animation"}
|
||||
|
||||
# Note the properties are moved to __init__
|
||||
# search for patterns advanced_objects, adv_obj
|
||||
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
Panel,
|
||||
)
|
||||
from random import (
|
||||
randint,
|
||||
uniform,
|
||||
)
|
||||
from mathutils import Vector
|
||||
from mathutils.geometry import intersect_point_line
|
||||
|
||||
|
||||
class Set_Up_Fold(Operator):
|
||||
bl_idname = "object.set_up_fold"
|
||||
bl_label = "Set Up Unfold"
|
||||
bl_description = ("Set up Faces and Bones for animation\n"
|
||||
"Needs an existing Active Mesh Object")
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return (obj is not None and obj.type == "MESH")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set()
|
||||
scn = bpy.context.scene
|
||||
adv_obj = scn.advanced_objects1
|
||||
obj = bpy.context.object
|
||||
dat = obj.data
|
||||
fac = dat.polygons
|
||||
ver = dat.vertices
|
||||
|
||||
# try to cleanup traces of previous actions
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=True)
|
||||
bpy.ops.object.mode_set()
|
||||
old_vg = [vg for vg in obj.vertex_groups if vg.name.startswith("bone.")]
|
||||
for vg in old_vg:
|
||||
obj.vertex_groups.remove(vg)
|
||||
|
||||
if "UnFold" in obj.modifiers:
|
||||
arm = obj.modifiers["UnFold"].object
|
||||
rig = arm.data
|
||||
try:
|
||||
scn.objects.unlink(arm)
|
||||
bpy.data.objects.remove(arm)
|
||||
bpy.data.armatures.remove(rig)
|
||||
except:
|
||||
pass
|
||||
obj.modifiers.remove(obj.modifiers["UnFold"])
|
||||
|
||||
# try to obtain the face sequence from the vertex weights
|
||||
if adv_obj.unfold_modo == "weight":
|
||||
if len(obj.vertex_groups):
|
||||
i = obj.vertex_groups.active.index
|
||||
W = []
|
||||
for f in fac:
|
||||
v_data = []
|
||||
for v in f.vertices:
|
||||
try:
|
||||
w = ver[v].groups[i].weight
|
||||
v_data.append((w, v))
|
||||
except:
|
||||
v_data.append((0, v))
|
||||
v_data.sort(reverse=True)
|
||||
v1 = ver[v_data[0][1]].co
|
||||
v2 = ver[v_data[1][1]].co
|
||||
cen = Vector(f.center)
|
||||
its = intersect_point_line(cen, v2, v1)
|
||||
head = v2.lerp(v1, its[1])
|
||||
peso = sum([x[0] for x in v_data])
|
||||
W.append((peso, f.index, cen, head))
|
||||
W.sort(reverse=True)
|
||||
S = [x[1:] for x in W]
|
||||
else:
|
||||
self.report({"INFO"}, "First paint a Weight Map for this object")
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
# separate the faces and sort them
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
bpy.ops.mesh.select_all(action="SELECT")
|
||||
bpy.ops.mesh.edge_split()
|
||||
bpy.ops.mesh.select_all(action="SELECT")
|
||||
|
||||
if adv_obj.unfold_modo == "cursor":
|
||||
bpy.context.tool_settings.mesh_select_mode = [True, True, True]
|
||||
bpy.ops.mesh.sort_elements(
|
||||
type="CURSOR_DISTANCE", elements={"VERT", "EDGE", "FACE"}
|
||||
)
|
||||
bpy.context.tool_settings.mesh_select_mode = [False, False, True]
|
||||
bpy.ops.object.mode_set()
|
||||
|
||||
# Get sequence of faces and edges from the face / vertex indices
|
||||
if adv_obj.unfold_modo != "weight":
|
||||
S = []
|
||||
for f in fac:
|
||||
E = list(f.edge_keys)
|
||||
E.sort()
|
||||
v1 = ver[E[0][0]].co
|
||||
v2 = ver[E[0][1]].co
|
||||
cen = Vector(f.center)
|
||||
its = intersect_point_line(cen, v2, v1)
|
||||
head = v2.lerp(v1, its[1])
|
||||
S.append((f.index, f.center, head))
|
||||
|
||||
# create the armature and the modifier
|
||||
arm = bpy.data.armatures.new("arm")
|
||||
rig = bpy.data.objects.new("rig_" + obj.name, arm)
|
||||
|
||||
# store the name for checking the right rig
|
||||
adv_obj.unfold_arm_name = rig.name
|
||||
rig.matrix_world = obj.matrix_world
|
||||
scn.objects.link(rig)
|
||||
scn.objects.active = rig
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
arm.display_type = "WIRE"
|
||||
rig.show_in_front = True
|
||||
mod = obj.modifiers.new("UnFold", "ARMATURE")
|
||||
mod.show_in_editmode = True
|
||||
mod.object = rig
|
||||
|
||||
# create bones and vertex groups
|
||||
root = arm.edit_bones.new("bone.000")
|
||||
root.tail = (0, 0, 0)
|
||||
root.head = (0, 0, 1)
|
||||
root.select = True
|
||||
vis = [False, True] + [False] * 30
|
||||
|
||||
for fb in S:
|
||||
f = fac[fb[0]]
|
||||
b = arm.edit_bones.new("bone.000")
|
||||
if adv_obj.unfold_flip:
|
||||
b.tail, b.head = fb[2], fb[1]
|
||||
else:
|
||||
b.tail, b.head = fb[1], fb[2]
|
||||
|
||||
b.align_roll(f.normal)
|
||||
b.select_set(False)
|
||||
b.layers = vis
|
||||
b.parent = root
|
||||
vg = obj.vertex_groups.new(name=b.name)
|
||||
vg.add(f.vertices, 1, "ADD")
|
||||
|
||||
bpy.ops.object.mode_set()
|
||||
|
||||
if adv_obj.unfold_modo == "weight":
|
||||
obj.vertex_groups.active_index = 0
|
||||
scn.objects.active = rig
|
||||
obj.select_set(False)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class Animate_Fold(Operator):
|
||||
bl_idname = "object.animate_fold"
|
||||
bl_label = "Animate Unfold"
|
||||
bl_description = ("Animate bones to simulate unfold. Starts on current frame\n"
|
||||
"Needs an existing Active Armature Object created in the previous step")
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
is_not_undo = False
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return (obj is not None and obj.type == "ARMATURE" and obj.is_visible(bpy.context.scene))
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
adv_obj = context.scene.advanced_objects1
|
||||
|
||||
if self.is_not_undo is True:
|
||||
layout.label(text="Warning:", icon="INFO")
|
||||
layout.label(text="The generated Armature was not selected or it was renamed")
|
||||
layout.label(text="The animation can fail if it is not generated by the previous step")
|
||||
layout.separator()
|
||||
layout.label(text="Expected Armature name:", icon="BONE_DATA")
|
||||
layout.label(text=str(adv_obj.unfold_arm_name), icon="TRIA_RIGHT")
|
||||
layout.label(text="To Continue press OK, to Cancel click Outside the Pop-up")
|
||||
layout.separator()
|
||||
else:
|
||||
return
|
||||
|
||||
def invoke(self, context, event):
|
||||
obj = bpy.context.object
|
||||
scn = bpy.context.scene
|
||||
adv_obj = scn.advanced_objects1
|
||||
|
||||
if obj.name != adv_obj.unfold_arm_name:
|
||||
self.is_not_undo = True
|
||||
return context.window_manager.invoke_props_dialog(self, width=400)
|
||||
else:
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.context.object
|
||||
scn = bpy.context.scene
|
||||
adv_obj = scn.advanced_objects1
|
||||
fra = scn.frame_current
|
||||
if obj.name != adv_obj.unfold_arm_name:
|
||||
self.report({"INFO"},
|
||||
"The generated rig was not selected or renamed. The animation can fail")
|
||||
# clear the animation and get the list of bones
|
||||
if obj.animation_data:
|
||||
obj.animation_data_clear()
|
||||
bpy.ops.object.mode_set(mode="POSE")
|
||||
bones = obj.pose.bones[0].children_recursive
|
||||
|
||||
if adv_obj.unfold_flip:
|
||||
rot = -3.141592
|
||||
else:
|
||||
rot = adv_obj.unfold_rot_max / 57.3
|
||||
|
||||
extra = adv_obj.unfold_rot_time * adv_obj.unfold_bounce
|
||||
ruido = max(adv_obj.unfold_rot_time + extra,
|
||||
adv_obj.unfold_sca_time) + adv_obj.unfold_fold_noise
|
||||
|
||||
len_bones = len(bones) if len(bones) != 0 else 1 # possible division by zero
|
||||
vel = (adv_obj.unfold_fold_duration - ruido) / len_bones
|
||||
|
||||
# introduce scale and rotation keyframes
|
||||
for a, b in enumerate(bones):
|
||||
t = fra + a * vel + randint(0, adv_obj.unfold_fold_noise)
|
||||
|
||||
if adv_obj.unfold_flip:
|
||||
b.scale = (1, 1, 1)
|
||||
elif adv_obj.unfold_from_point:
|
||||
b.scale = (0, 0, 0)
|
||||
else:
|
||||
b.scale = (1, 0, 0)
|
||||
|
||||
if not adv_obj.unfold_flip:
|
||||
b.keyframe_insert("scale", frame=t)
|
||||
b.scale = (1, 1, 1)
|
||||
b.keyframe_insert("scale", frame=t + adv_obj.unfold_sca_time)
|
||||
|
||||
if adv_obj.unfold_rot_max:
|
||||
b.rotation_mode = "XYZ"
|
||||
if adv_obj.unfold_wiggle_rot:
|
||||
euler = (uniform(-rot, rot), uniform(-rot, rot), uniform(-rot, rot))
|
||||
else:
|
||||
euler = (rot, 0, 0)
|
||||
|
||||
b.rotation_euler = euler
|
||||
b.keyframe_insert("rotation_euler", frame=t)
|
||||
|
||||
if adv_obj.unfold_bounce:
|
||||
val = adv_obj.unfold_bounce * -.10
|
||||
b.rotation_euler = (val * euler[0], val * euler[1], val * euler[2])
|
||||
b.keyframe_insert(
|
||||
"rotation_euler", frame=t + adv_obj.unfold_rot_time + .25 * extra
|
||||
)
|
||||
|
||||
val = adv_obj.unfold_bounce * .05
|
||||
b.rotation_euler = (val * euler[0], val * euler[1], val * euler[2])
|
||||
b.keyframe_insert(
|
||||
"rotation_euler", frame=t + adv_obj.unfold_rot_time + .50 * extra
|
||||
)
|
||||
|
||||
val = adv_obj.unfold_bounce * -.025
|
||||
b.rotation_euler = (val * euler[0], val * euler[1], val * euler[2])
|
||||
b.keyframe_insert(
|
||||
"rotation_euler", frame=t + adv_obj.unfold_rot_time + .75 * extra
|
||||
)
|
||||
|
||||
b.rotation_euler = (0, 0, 0)
|
||||
b.keyframe_insert(
|
||||
"rotation_euler", frame=t + adv_obj.unfold_rot_time + extra
|
||||
)
|
||||
self.is_not_undo = False
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PanelFOLD(Panel):
|
||||
bl_label = "Unfold Transition"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "TOOLS"
|
||||
bl_category = "Create"
|
||||
bl_context = "objectmode"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
adv_obj = context.scene.advanced_objects1
|
||||
|
||||
box = layout.box()
|
||||
col = box.column()
|
||||
col.operator("object.set_up_fold", text="1. Set Up Unfold")
|
||||
col.separator()
|
||||
col.label(text="Unfold Mode:")
|
||||
col.prop(adv_obj, "unfold_modo")
|
||||
col.prop(adv_obj, "unfold_flip")
|
||||
|
||||
box = layout.box()
|
||||
col = box.column(align=True)
|
||||
col.operator("object.animate_fold", text="2. Animate Unfold")
|
||||
col.separator()
|
||||
col.prop(adv_obj, "unfold_fold_duration")
|
||||
col.prop(adv_obj, "unfold_sca_time")
|
||||
col.prop(adv_obj, "unfold_rot_time")
|
||||
col.prop(adv_obj, "unfold_rot_max")
|
||||
|
||||
row = col.row(align=True)
|
||||
row.prop(adv_obj, "unfold_fold_noise")
|
||||
row.prop(adv_obj, "unfold_bounce")
|
||||
row = col.row(align=True)
|
||||
row.prop(adv_obj, "unfold_wiggle_rot")
|
||||
|
||||
if not adv_obj.unfold_flip:
|
||||
row.prop(adv_obj, "unfold_from_point")
|
||||
|
||||
|
||||
classes = (
|
||||
Set_Up_Fold,
|
||||
Animate_Fold,
|
||||
PanelFOLD,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in classes:
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
Loading…
Reference in New Issue