├── .gitignore ├── README.md ├── SKRoll ├── SKRoll1.blend ├── SKRoll2.blend ├── SKRoll3.blend ├── ard_MainControll.py ├── ard_Shaft.py ├── arduino_funcs.py ├── state.py └── utils.py ├── add_mesh_lowpoly_rock.py ├── game_engine ├── mouse_look.blend ├── scripts │ ├── mouse_look.py │ └── utilities.py └── textures │ └── colgrid512.png ├── gcode ├── evaluate_gcode.py ├── inkscape_star_export.gcode ├── io_curve_gcode │ └── __init__.py └── test.blend ├── mesh_normalize_deform_weight.py ├── mesh_offset_edges.py ├── mod_imp.py ├── modified ├── HairNet_modified.py ├── add_mesh_ant_landscape_modified.py └── space_view3d_viewport_roll_modified.py ├── object_apply_transform_multiuser.py ├── offset_unittest.blend ├── space_view3d_select_mode_pie.py └── space_view3d_set_smooth.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.orig 2 | *.bak 3 | *.~ 4 | *.swp 5 | *.blend[1-9] 6 | __pycache__ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Python scripts for blender. 2 | -------------------------------------------------------------------------------- /SKRoll/SKRoll1.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/underdoggit2/BlenderPythonScripts/9e688b8be56f7488fd03c94dc4151c8ee34aa817/SKRoll/SKRoll1.blend -------------------------------------------------------------------------------- /SKRoll/SKRoll2.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/underdoggit2/BlenderPythonScripts/9e688b8be56f7488fd03c94dc4151c8ee34aa817/SKRoll/SKRoll2.blend -------------------------------------------------------------------------------- /SKRoll/SKRoll3.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/underdoggit2/BlenderPythonScripts/9e688b8be56f7488fd03c94dc4151c8ee34aa817/SKRoll/SKRoll3.blend -------------------------------------------------------------------------------- /SKRoll/ard_MainControll.py: -------------------------------------------------------------------------------- 1 | import state 2 | from arduino_funcs import * 3 | 4 | class SawBase(state.StateFuncs): 5 | _initial_ = 'M_WAIT' 6 | _properties_ = {'num_sk': 0} 7 | _read_pins_ = {'p_start': ...} 8 | def M_WAIT_main(ctrlr): 9 | if digital_read(ctrlr, 'START'): 10 | state.set_property(ctrlr, 'num_sk', 0) 11 | state.goto_master(ctrlr, 'M_START') 12 | 13 | def M_START_init(ctrlr): 14 | state.increment_property(ctrlr, 'num_sk') 15 | def M_START_main(ctrlr): 16 | if state.get_property(ctrlr, 'num_sk') <= 3: 17 | state.set_sequence(ctrlr, 18 | 'PUSH_SAW', 19 | 'FORWARD', 20 | 'PULL_SAW', 21 | 'REACHED', 22 | 'BACKWARD', 23 | 'ROT_SHAFT', 24 | 'M_START', 25 | ) 26 | state.next_state(ctrlr) 27 | else: 28 | state.goto_master(ctrlr, 'M_WAIT') 29 | 30 | def PUSH_SAW_init(ctrlr): 31 | digital_write(ctrlr, 'SawArm', True) 32 | def PUSH_SAW_main(ctrlr): 33 | if state.statetime(ctrlr) > 1: 34 | digital_write(ctrlr, 'Saw', True) 35 | state.next_state(ctrlr) 36 | 37 | def FORWARD_init(ctrlr): 38 | digital_write(ctrlr, 'SawBase_FOR', True) 39 | def FORWARD_main(ctrlr): 40 | if digital_read(ctrlr, 'SawBaseEndTouch'): 41 | digital_write(ctrlr, 'SawBase_FOR', False) 42 | state.next_state(ctrlr) 43 | 44 | def PULL_SAW_init(ctrlr): 45 | digital_write(ctrlr, 'SawArm', False) 46 | def PULL_SAW_main(ctrlr): 47 | if state.statetime(ctrlr) > 1: 48 | digital_write(ctrlr, 'Saw', False) 49 | state.next_state(ctrlr) 50 | 51 | def REACHED_main(ctrlr): 52 | if state.statetime(ctrlr) > 1: 53 | state.next_state(ctrlr) 54 | 55 | def BACKWARD_init(ctrlr): 56 | digital_write(ctrlr, 'SawBase_BACK', True) 57 | def BACKWARD_main(ctrlr): 58 | if digital_read(ctrlr, 'SawBaseStartTouch'): 59 | digital_write(ctrlr, 'SawBase_BACK', False) 60 | state.next_state(ctrlr) 61 | 62 | def ROT_SHAFT_init(ctrlr): 63 | act = ctrlr.actuators['Shaft_ROT'] 64 | act.body = '120' 65 | digital_write(ctrlr, 'Shaft_ROT', True) 66 | def ROT_SHAFT_main(ctrlr): 67 | if digital_read(ctrlr, 'Shaft_ROTATED'): 68 | state.next_state(ctrlr) 69 | 70 | loop = SawBase() 71 | -------------------------------------------------------------------------------- /SKRoll/ard_Shaft.py: -------------------------------------------------------------------------------- 1 | import state 2 | 3 | class Shaft(state.StateFuncs): 4 | _initial_ = 'M_WAIT' 5 | _properties_ = {'angle': 0} 6 | def M_WAIT_main(ctrlr): 7 | sensor = ctrlr.sensors['ROT'] 8 | if sensor.positive: 9 | state.set_property(ctrlr, 'angle', int(sensor.bodies[0], 10)) 10 | state.set_sequence(ctrlr, 11 | 'ROTATING', 12 | 'M_WAIT', 13 | ) 14 | state.next_state(ctrlr) 15 | def ROTATING_init(ctrlr): 16 | ctrlr.activate(ctrlr.actuators['rotation']) 17 | def ROTATING_main(ctrlr): 18 | if state.stateframe(ctrlr) > state.get_property(ctrlr, 'angle'): 19 | ctrlr.deactivate(ctrlr.actuators['rotation']) 20 | ctrlr.activate(ctrlr.actuators['Shaft_ROTATED']) 21 | print(ctrlr.owner.localOrientation.to_euler('XYZ')) 22 | state.next_state(ctrlr) 23 | 24 | loop = Shaft() 25 | -------------------------------------------------------------------------------- /SKRoll/arduino_funcs.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | "digital_read", "digital_write", 3 | ] 4 | 5 | def digital_read(ctrlr, sensor_name): 6 | """ 7 | setup:: 8 | pinMode({pin}, INPUT) 9 | main:: 10 | digitalRead({pin}) 11 | """ 12 | return ctrlr.sensors[sensor_name].positive 13 | 14 | def digital_write(ctrlr, actuator_name, value): 15 | if value is True: 16 | ctrlr.activate(ctrlr.actuators[actuator_name]) 17 | else: 18 | ctrlr.deactivate(ctrlr.actuators[actuator_name]) 19 | -------------------------------------------------------------------------------- /SKRoll/state.py: -------------------------------------------------------------------------------- 1 | import re 2 | import collections.abc 3 | import time 4 | 5 | PROP_STATE = '_state_' 6 | PROP_PREVSTATE = '_previous_state_' 7 | PROP_STATE_SEQ = '_state_sequence_' 8 | PROP_SEQ_INDEX = '_sequence_index_' 9 | PROP_STATE_FRAME = '_state_frame_' 10 | PROP_STATE_START_TIME = '_state_time_' 11 | PROP_TIMER = '_timer_' 12 | 13 | 14 | MASTER_ID = 'M_' 15 | 16 | def get_property(ctrlr, name): 17 | return ctrlr.owner[name] 18 | 19 | def set_property(ctrlr, name, value): 20 | owner = ctrlr.owner 21 | assert name in owner 22 | owner[name] = value 23 | 24 | def increment_property(ctrlr, name): 25 | ctrlr.owner[name] += 1 26 | def decrement_property(ctrlr, name): 27 | ctrlr.owner[name] -= 1 28 | 29 | def set_sequence(ctrlr, *seq): 30 | assert seq[-1].startswith(MASTER_ID) 31 | owner = ctrlr.owner 32 | owner[PROP_STATE_SEQ][:] = seq 33 | owner[PROP_SEQ_INDEX] = -1 34 | 35 | def next_state(ctrlr): 36 | owner = ctrlr.owner 37 | owner[PROP_SEQ_INDEX] += 1 38 | _change_state(owner, owner[PROP_STATE_SEQ][owner[PROP_SEQ_INDEX]]) 39 | 40 | def prev_state(ctrlr): 41 | owner = ctrlr.owner 42 | owner[PROP_SEQ_INDEX] -= 1 43 | _change_state(owner, owner[PROP_STATE_SEQ][owner[PROP_SEQ_INDEX]]) 44 | 45 | def goto_master(ctrlr, master_state): 46 | assert master_state.startswith(MASTER_ID) 47 | owner = ctrlr.owner 48 | _change_state(owner, master_state) 49 | del owner[PROP_STATE_SEQ][:] 50 | owner[PROP_SEQ_INDEX] = -1 51 | 52 | def statetime(ctrlr): 53 | return time.perf_counter() - ctrlr.owner[PROP_STATE_START_TIME] 54 | 55 | def stateframe(ctrlr): 56 | return ctrlr.owner[PROP_STATE_FRAME] 57 | 58 | def timer_set(ctrlr): 59 | ctrlr.owner[PROP_TIMER] = timer.perf_counter() 60 | 61 | def timer_get(ctrlr): 62 | return time.perf_counter() - ctrlr.owner[PROP_TIMER] 63 | 64 | def do_nothing(ctrlr): 65 | pass 66 | 67 | 68 | class StateFuncsMeta(type): 69 | statefunc_pattern = re.compile( 70 | """ 71 | ([A-Z][A-Z0-9_]+) # the state name, all upper case 72 | _ # underscore 73 | (init|main|clean) # the func role 74 | $ # 75 | """, re.VERBOSE) 76 | def __new__(mcls, name, bases, namespace, **kwargs): 77 | result = type.__new__(mcls, name, bases, namespace, **kwargs) 78 | assert result._initial_.isupper() and \ 79 | result._initial_.startswith(MASTER_ID) 80 | assert isinstance(result._properties_, dict) 81 | 82 | result._states = dict() 83 | result._user_props = result._properties_.copy() 84 | for base in bases: 85 | if isinstance(base, mcls): 86 | result._states.update(base._states) 87 | result._user_props.update(base._user_props) 88 | 89 | state_names = set() 90 | for attrname in namespace: 91 | match = mcls.statefunc_pattern.match(attrname) 92 | if match: 93 | assert isinstance(namespace[attrname], 94 | collections.abc.Callable) 95 | state_names.add(match.group(1)) 96 | 97 | for sname in state_names: 98 | result._states[sname] = ( 99 | getattr(result, sname + "_init", do_nothing), 100 | getattr(result, sname + "_main", do_nothing), 101 | getattr(result, sname + "_clean", do_nothing), 102 | ) 103 | 104 | return result 105 | 106 | @staticmethod 107 | def main(cls, ctrlr): 108 | owner = ctrlr.owner 109 | current_state = owner[PROP_STATE] 110 | owner[PROP_STATE_FRAME] += 1 111 | if current_state in cls._states: 112 | statefunc_init, statefunc_main, statefunc_clean = \ 113 | cls._states[current_state] 114 | #if previous_state != current_state: 115 | if owner[PROP_STATE_FRAME] == 1: 116 | statefunc_init(ctrlr) 117 | owner[PROP_PREVSTATE] = current_state 118 | statefunc_main(ctrlr) 119 | cls.INTERRUPT(ctrlr, statefunc_clean) 120 | else: 121 | cls.INTERRUPT(ctrlr, do_nothing) 122 | 123 | def __call__(cls): 124 | def loop(ctrlr): 125 | owner = ctrlr.owner 126 | if PROP_STATE not in owner: 127 | owner[PROP_STATE] = cls._initial_ 128 | owner[PROP_PREVSTATE] = '' 129 | owner[PROP_STATE_SEQ] = list() 130 | owner[PROP_SEQ_INDEX] = -1 131 | owner[PROP_STATE_FRAME] = 0 132 | owner[PROP_STATE_START_TIME] = \ 133 | owner[PROP_TIMER] = time.perf_counter() 134 | for name, value in cls._user_props.items(): 135 | owner[name] = value 136 | __class__.main(cls, ctrlr) 137 | return loop 138 | 139 | 140 | class StateFuncs(metaclass=StateFuncsMeta): 141 | _initial_ = 'M_EMPTY' 142 | _properties_ = {} 143 | def M_EMPTY_main(ctrlr): 144 | pass 145 | 146 | def INTERRUPT(ctrlr, statefunc_clean): 147 | pass 148 | 149 | def _change_state(owner, new_state): 150 | assert new_state.isupper() 151 | owner[PROP_PREVSTATE] = owner[PROP_STATE] 152 | owner[PROP_STATE] = new_state 153 | owner[PROP_STATE_FRAME] = 0 154 | owner[PROP_STATE_START_TIME] = time.perf_counter() 155 | 156 | 157 | 158 | if __name__ == '__main__': 159 | class TestStateFuncs(StateFuncs): 160 | _initial_ = 'M_INITIAL' 161 | def M_INITIAL_init(ctrlr): 162 | print("INITIAL_init function") 163 | def M_INITIAL_main(ctrlr): 164 | print("INITIAL_main function") 165 | def M_INITIAL_clean(ctrlr): 166 | print("INITIAL_clean function") 167 | def NEXT_STATE_main(ctrlr): 168 | print("NEXT_STATE_main function") 169 | 170 | class TestController: 171 | def __init__(self): 172 | self.owner = dict() 173 | 174 | c = TestController() 175 | #loop = TestStateFuncs.get_loopfunc('M_INITIAL') 176 | #loop(c) 177 | #print(c.owner) 178 | #loop(c) 179 | #print(c.owner) 180 | TestStateFuncs(c) 181 | print(c.owner) 182 | 183 | -------------------------------------------------------------------------------- /SKRoll/utils.py: -------------------------------------------------------------------------------- 1 | def sensors_any(ctrlr): 2 | for sensor in ctrlr.sensors: 3 | if sensor.positive: 4 | return True 5 | else: 6 | return False 7 | def sensors_all(ctrlr): 8 | for sensor in ctrlr.sensors: 9 | if not sensor.positive: 10 | return False 11 | else: 12 | return True 13 | 14 | def or_activate(ctrlr): 15 | if sensors_any(ctrlr): 16 | for act in ctrlr.actuators: 17 | ctrlr.activate(act) 18 | 19 | def and_activate(ctrlr): 20 | if sensors_all(ctrlr): 21 | for act in ctrlr.actuators: 22 | ctrlr.activate(act) 23 | 24 | def or_deactivate(ctrlr): 25 | if sensors_any(ctrlr): 26 | for act in ctrlr.actuators: 27 | ctrlr.deactivate(act) 28 | 29 | def and_deactivate(ctrlr): 30 | if sensors_all(ctrlr): 31 | for act in ctrlr.actuators: 32 | ctrlr.deactivate(act) 33 | -------------------------------------------------------------------------------- /add_mesh_lowpoly_rock.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN GPL LICENSE BLOCK ***** 2 | # 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ***** END GPL LICENCE BLOCK ***** 19 | 20 | # 21 | 22 | bl_info = { 23 | "name": "LowPoly Rock", 24 | "author": "Hidesato Ikeya", 25 | "version": (0, 1, 9), 26 | "blender": (2, 68, 0), 27 | "location": "VIEW3D > ADD > Mesh", 28 | "description": "LowPoly Rock", 29 | "warning": "", 30 | "wiki_url": "", 31 | "tracker_url": "", 32 | "category": "Add Mesh"} 33 | 34 | import bpy 35 | import bmesh 36 | from mathutils import Matrix 37 | from math import radians 38 | from random import seed, uniform 39 | 40 | ROCK_NAME = "LowPolyRock" 41 | ORIGIN_NAME = ROCK_NAME + "DisplaceOrigin" 42 | TEXTURE_NAME = ROCK_NAME + "Texture" 43 | ANGLE_MAX = radians(90) 44 | 45 | 46 | def get_basemesh(context, subdiv=5, radius=1.0, ratio=(1., 1., 1.)): 47 | me = context.blend_data.meshes.new('tempmeshname') 48 | bm = bmesh.new() 49 | bm.from_mesh(me) 50 | mat = Matrix() 51 | mat[0][0], mat[1][1], mat[2][2] = ratio 52 | bmesh.ops.create_icosphere( 53 | bm, subdivisions=subdiv, diameter=radius, matrix=mat) 54 | bm.to_mesh(me) 55 | return me 56 | 57 | def get_texture(context, name, size=1.0, brightness=.8, contrast=.8, 58 | weights=(1.0, .3, .0)): 59 | tex = context.blend_data.textures.new(name, 'VORONOI') 60 | tex.noise_scale = size 61 | tex.intensity = brightness 62 | tex.contrast = contrast 63 | tex.weight_1, tex.weight_2, tex.weight_3 = weights 64 | tex.use_color_ramp = True 65 | 66 | ramp = tex.color_ramp 67 | ramp.interpolation = 'B_SPLINE' 68 | ramp.elements[0].color = (.0, .0, .0, 1.0) 69 | ramp.elements[0].position = .5 70 | return tex 71 | 72 | def create_rock(context, subdiv, radius, size_ratio, 73 | noise_center, noise_size, noise_brightness, 74 | sharpness, displace_midlevel, displace_strength, 75 | voronoi_weights, simplicity, collapse_ratio): 76 | me = get_basemesh(context, subdiv, radius, size_ratio) 77 | rock = context.blend_data.objects.new(ROCK_NAME, me) 78 | rock.show_all_edges = True 79 | ix_dot = rock.name.rfind('.') 80 | if ix_dot != -1: 81 | number = rock.name[ix_dot:] 82 | else: 83 | number = "" 84 | context.scene.objects.link(rock) 85 | context.scene.objects.active = rock 86 | 87 | # Displacement 88 | noise_origin = \ 89 | context.blend_data.objects.new(ORIGIN_NAME + number, None) 90 | noise_origin.location = noise_center 91 | noise_origin.location *= radius 92 | context.scene.objects.link(noise_origin) 93 | disp = rock.modifiers.new('displace', 'DISPLACE') 94 | disp.direction = 'NORMAL' 95 | disp.mid_level = displace_midlevel 96 | disp.strength = radius * displace_strength 97 | disp.texture_coords = 'OBJECT' 98 | disp.texture_coords_object = noise_origin 99 | disp.texture = get_texture( 100 | context, TEXTURE_NAME + number, size=radius * noise_size, 101 | brightness=noise_brightness, 102 | contrast=sharpness, weights=voronoi_weights) 103 | 104 | # Collapse 105 | collapse = rock.modifiers.new('collapse', 'DECIMATE') 106 | collapse.decimate_type = 'COLLAPSE' 107 | collapse.ratio = collapse_ratio 108 | 109 | # Planer 110 | planer = rock.modifiers.new('planer', 'DECIMATE') 111 | planer.decimate_type = 'DISSOLVE' 112 | planer.angle_limit = simplicity * ANGLE_MAX 113 | planer.use_dissolve_boundaries = True 114 | 115 | return rock, noise_origin 116 | 117 | 118 | class LowPolyRock(bpy.types.Operator): 119 | """LowPoly Rock""" 120 | bl_idname = "mesh.lowpoly_rock_add" 121 | bl_label = "LowPoly Rock" 122 | bl_options = {'REGISTER', 'UNDO', 'PRESET'} 123 | 124 | num_rock = bpy.props.IntProperty( 125 | name="Number", min=1, max=9, default=1, 126 | description="Number of rocks") 127 | size = bpy.props.FloatProperty( 128 | name="Size", min=.0, default=1.0, precision=3, step=0.01) 129 | size_ratio = bpy.props.FloatVectorProperty( 130 | name="Size Ratio", size=3, min=.0, default=(1., 1., 1.), 131 | subtype='TRANSLATION', step=0.1, precision=2, description="Size ratio") 132 | displace_midlevel = bpy.props.FloatProperty( 133 | name="Midlevel", min=.0, max=1.0, default=.5, precision=3, step=0.1) 134 | noise_center = bpy.props.FloatVectorProperty( 135 | name="Noise Center", size=3, step=0.1, subtype='TRANSLATION', 136 | description="Displacement noise texture origin") 137 | simplicity = bpy.props.FloatProperty( 138 | name="Simplicity", min=.0, max=1.0, default=0.25, 139 | precision=2, step=0.1, description="Reduce polygons") 140 | sharpness = bpy.props.FloatProperty( 141 | name="Sharpness", min=.0, max=2.0, default=.8, precision=3, step=0.1) 142 | edge_split = bpy.props.BoolProperty( 143 | name="Edge Split", default=True, 144 | description="Shade smooth and add edge split modifier") 145 | 146 | random_seed = bpy.props.IntProperty( 147 | name="Random Seed", min=-1, default=0, 148 | description="Randome seed (set -1 to use system clock)") 149 | size_min = bpy.props.FloatProperty( 150 | name="Size Min", min=-1.0, max=.0, default=-.3, precision=3, step=0.01) 151 | size_max = bpy.props.FloatProperty( 152 | name="Size Max", min=.0, default=.3, precision=3, step=0.01) 153 | size_ratio_min = bpy.props.FloatVectorProperty( 154 | name="Ratio Min", size=3, min=-1.0, max=.0, default=(-.2, -.2, -.2), 155 | precision=3, step=0.01) 156 | size_ratio_max = bpy.props.FloatVectorProperty( 157 | name="Ratio Max", size=3, min=.0, default=(.2, .2, .2), 158 | precision=3, step=0.01) 159 | 160 | keep_modifiers = bpy.props.BoolProperty( 161 | name="Keep Modifiers", default=False, 162 | description="Keep modifiers") 163 | advanced_menu = bpy.props.BoolProperty( 164 | name="Advanced Menu", default=False, 165 | description="Display advanced menu") 166 | voronoi_weights = bpy.props.FloatVectorProperty( 167 | name="Voronoi Weights", min=-1.0, max=1.0, size=3, 168 | default=(1.,.3,.0), step=0.1, description="Voronoi Weights") 169 | displace_strength = bpy.props.FloatProperty( 170 | name="Strength", min=.0, default=1.0, precision=3, step=0.1) 171 | noise_size = bpy.props.FloatProperty( 172 | name="Nsize", min=.0, default=1.0, precision=3, step=0.1) 173 | noise_brightness = bpy.props.FloatProperty( 174 | name="Nbright", min=.0, max=1.5, default=.8, precision=3, step=0.1) 175 | subdiv = bpy.props.IntProperty( 176 | name="Subdivision", min=1, max=7, default=5, 177 | description="Icosphere subdivision") 178 | collapse_ratio = bpy.props.FloatProperty( 179 | name="Collapse Ratio", min=.0, max=1.0, default=.06, 180 | precision=3, step=0.01,) 181 | 182 | @classmethod 183 | def poll(self, context): 184 | return context.mode == 'OBJECT' 185 | 186 | def draw(self, context): 187 | layout = self.layout 188 | 189 | basic = layout.box() 190 | basic.label("Basic Settings:") 191 | basic.prop(self, 'num_rock') 192 | col = basic.column(align=True) 193 | col.prop(self, 'size') 194 | col.label("Size Ratio:") 195 | col.prop(self, 'size_ratio', text="") 196 | col.prop(self, 'displace_midlevel') 197 | 198 | col = basic.column(align=True) 199 | col.label("Noise Center:") 200 | row = col.row() 201 | row.prop(self, 'noise_center', text="") 202 | 203 | basic.prop(self, 'simplicity') 204 | basic.prop(self, 'sharpness') 205 | basic.prop(self, 'edge_split') 206 | 207 | random = layout.box() 208 | random.label("Random Settings:") 209 | random.prop(self, 'random_seed') 210 | 211 | col = random.column(align=True) 212 | col.label('Size Range:') 213 | row = col.row(align=True) 214 | row.prop(self, 'size_min', text='min') 215 | row.prop(self, 'size_max', text='max') 216 | 217 | col = random.column(align=True) 218 | col.label('Size Ratio Range:') 219 | row = col.row() 220 | col = row.column(align=True) 221 | col.prop(self, 'size_ratio_min', text="") 222 | col = row.column(align=True) 223 | col.prop(self, 'size_ratio_max', text="") 224 | 225 | advanced = layout.box() 226 | advanced.prop(self, 'advanced_menu', text="Advanced Settings:") 227 | if self.advanced_menu: 228 | advanced.prop(self, 'keep_modifiers') 229 | advanced.prop(self, 'displace_strength') 230 | advanced.prop(self, 'voronoi_weights') 231 | advanced.prop(self, 'noise_size') 232 | advanced.prop(self, 'noise_brightness') 233 | advanced.prop(self, 'subdiv') 234 | advanced.prop(self, 'collapse_ratio') 235 | 236 | def execute(self, context): 237 | bpy.ops.object.select_all(action='DESELECT') 238 | 239 | radius = self.size 240 | size_ratio = self.size_ratio.copy() 241 | noise_center = self.noise_center.copy() 242 | 243 | random_seed = self.random_seed 244 | if random_seed == -1: 245 | random_seed = None 246 | seed(random_seed) 247 | 248 | location = context.scene.cursor_location.copy() 249 | rocks = [None] * self.num_rock 250 | for n in range(self.num_rock): 251 | rock, noise_origin = create_rock( 252 | context, self.subdiv, radius, size_ratio, 253 | noise_center, self.noise_size, self.noise_brightness, 254 | self.sharpness, self.displace_midlevel, self.displace_strength, 255 | self.voronoi_weights, self.simplicity, self.collapse_ratio) 256 | rocks[n] = rock 257 | 258 | if self.keep_modifiers: 259 | rock.location = location 260 | noise_origin.location += location 261 | else: 262 | context.scene.update() 263 | me_orig = rock.data 264 | tex = rock.modifiers['displace'].texture 265 | rock.data = rock.to_mesh(context.scene, True, 'PREVIEW') 266 | context.blend_data.meshes.remove(me_orig) 267 | rock.modifiers.clear() 268 | rock.location = location 269 | context.scene.objects.unlink(noise_origin) 270 | context.blend_data.objects.remove(noise_origin) 271 | context.blend_data.textures.remove(tex) 272 | 273 | if self.edge_split: 274 | rock.select = True 275 | bpy.ops.object.shade_smooth() 276 | split = rock.modifiers.new('split', 'EDGE_SPLIT') 277 | split.use_edge_angle = True 278 | split.use_edge_sharp = False 279 | split.split_angle = .0 280 | rock.select = False 281 | 282 | rock.data.name = rock.name 283 | 284 | radius = self.size * (1.0 + uniform(self.size_min, self.size_max)) 285 | for i in range(3): 286 | noise_center[i] = self.noise_center[i] + uniform(-1000, 1000) 287 | size_ratio[i] = self.size_ratio[i] * \ 288 | (1.0 + uniform(self.size_ratio_min[i], self.size_ratio_max[i])) 289 | if n % 2 == 0: 290 | location.x = context.scene.cursor_location.x \ 291 | + self.size * 1.6 * (n // 2 + 1) 292 | else: 293 | location.x = context.scene.cursor_location.x \ 294 | - self.size * 1.6 * (n // 2 + 1) 295 | 296 | for rock in rocks: 297 | rock.select = True 298 | 299 | return {'FINISHED'} 300 | 301 | def invoke(self, context, event): 302 | return self.execute(context) 303 | 304 | 305 | def draw_item(self, context): 306 | self.layout.operator_context = 'INVOKE_DEFAULT' 307 | self.layout.operator(LowPolyRock.bl_idname, text="LowPoly Rock", icon="PLUGIN") 308 | 309 | def register(): 310 | bpy.utils.register_module(__name__) 311 | bpy.types.INFO_MT_mesh_add.append(draw_item) 312 | 313 | def unregister(): 314 | bpy.utils.unregister_module(__name__) 315 | bpy.types.INFO_MT_mesh_add.remove(draw_item) 316 | 317 | if __name__ == '__main__': 318 | register() 319 | -------------------------------------------------------------------------------- /game_engine/mouse_look.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/underdoggit2/BlenderPythonScripts/9e688b8be56f7488fd03c94dc4151c8ee34aa817/game_engine/mouse_look.blend -------------------------------------------------------------------------------- /game_engine/scripts/mouse_look.py: -------------------------------------------------------------------------------- 1 | from bge import logic 2 | from bge.logic import KX_INPUT_NONE, KX_INPUT_JUST_ACTIVATED, KX_INPUT_ACTIVE, KX_INPUT_JUST_RELEASED 3 | from bge import render 4 | from bge import events 5 | from bge import types 6 | from bge import constraints 7 | from mathutils import Matrix, Vector, Quaternion 8 | 9 | from . import utilities 10 | from .utilities import ANGLE_0, ANGLE_90, ANGLE_180, ANGLE_360, AXIS_Z 11 | from math import cos, sin, acos, pi 12 | 13 | MAT_IDENTITY3 = Matrix.Identity(3) 14 | 15 | class MouseLook(types.KX_GameObject): 16 | sensitivity = 0.001 17 | upper_limit = ANGLE_180 18 | lower_limit = ANGLE_0 19 | 20 | speed = 10.0 21 | body_height = 1.8 22 | body_width = 1.6 23 | 24 | jump_speed = 10.0 25 | 26 | division_onground = 8 27 | division_angle = 2 * pi / division_onground 28 | 29 | def __init__(self, old_owner): 30 | types.KX_GameObject.__init__(self) 31 | #render.showMouse(True) 32 | logic.mouse.position = .5, .5 33 | 34 | self.phys_id = self.getPhysicsId() 35 | 36 | self.head = self.foot = None 37 | for c in self.children: 38 | if "HEAD" in c: 39 | self.head = c 40 | elif "FOOT" in c: 41 | self.foot = c 42 | 43 | self.walk_direction = Vector.Fill(3, .0) 44 | 45 | self.jumping = False 46 | 47 | def main(self): 48 | self.detect_onground() 49 | self.look() 50 | self.move() 51 | 52 | def detect_onground(self): 53 | offset = 0.2 54 | dist = 0.21 55 | pos_w = self.worldPosition.copy() 56 | ori = self.worldOrientation.col 57 | waxis_ly = ori[1] 58 | waxis_lz = ori[2] 59 | pos_w += waxis_lz * offset 60 | ground = self.rayCast(pos_w - waxis_lz, pos_w, dist)[2] 61 | 62 | if not ground: 63 | quat = Quaternion(waxis_lz, self.division_angle) 64 | v = waxis_ly * (self.body_width / 2) 65 | rayCast = self.rayCast 66 | for i in range(self.division_onground): 67 | vec_from = pos_w + v 68 | ground = rayCast(vec_from - waxis_lz, vec_from, dist)[2] 69 | if ground: 70 | break 71 | v.rotate(quat) 72 | 73 | self.ground = ground 74 | 75 | def rotate_foot(self): 76 | if self.ground: 77 | normal = self.worldOrientation.inverted() * self.ground 78 | quat = AXIS_Z.rotation_difference(normal) 79 | self.foot.localOrientation = quat 80 | else: 81 | self.foot.localOrientation = MAT_IDENTITY3 82 | 83 | def look(self): 84 | sense = self.sensitivity 85 | x = (0.5 - logic.mouse.position[0]) * render.getWindowWidth() 86 | y = (0.5 - logic.mouse.position[1]) * render.getWindowHeight() 87 | if abs(x) <= 1.0: 88 | x = 0.0 89 | if abs(y) <= 1.0: 90 | y = 0.0 91 | x *= sense 92 | y *= sense 93 | 94 | head = self.head or self 95 | laxis_z = head.localOrientation.col[2] 96 | #laxis_z = self.getAxisVect(AXIS_Z) 97 | angle = acos(laxis_z.dot(AXIS_Z)) 98 | upper_limit = self.upper_limit - 0.01 99 | lower_limit = self.lower_limit + 0.01 100 | if angle + y > upper_limit: 101 | y = upper_limit - angle 102 | elif angle + y < lower_limit: 103 | y = lower_limit - angle 104 | 105 | self.applyRotation((0, 0, x), False) 106 | head.applyRotation((y, 0, 0), True) 107 | 108 | logic.mouse.position = .5, .5 109 | 110 | def move(self): 111 | key = logic.keyboard 112 | 113 | walk_direction = self.walk_direction 114 | walk_direction[:] = (0, 0, 0) 115 | speed = self.speed 116 | 117 | if key.events[events.WKEY]: 118 | walk_direction[1] = 1.0 119 | if key.events[events.SKEY]: 120 | walk_direction[1] = -1.0 121 | 122 | if key.events[events.DKEY]: 123 | walk_direction[0] = 1.0 124 | if key.events[events.AKEY]: 125 | walk_direction[0] = -1.0 126 | 127 | walk_direction.normalize() 128 | walk_direction *= speed 129 | 130 | if self.phys_id != 0: 131 | self.rotate_foot() 132 | 133 | footlocal = self.foot.localOrientation 134 | walk_direction[:] = footlocal * walk_direction 135 | walk_lz = walk_direction[2] 136 | velo_lz = self.getLinearVelocity(True)[2] 137 | 138 | ground = self.ground 139 | space = key.events[events.SPACEKEY] 140 | 141 | if ground or velo_lz < 3.0: 142 | self.jumping = False 143 | 144 | if self.jumping: 145 | if not space: 146 | walk_direction[2] = velo_lz / 2 147 | self.jumping = False 148 | else: 149 | walk_direction[2] = velo_lz 150 | elif ground: 151 | if space: 152 | walk_direction[2] += self.jump_speed 153 | self.jumping = True 154 | else: 155 | walk_direction -= footlocal.col[2] 156 | else: 157 | walk_direction[2] = velo_lz 158 | 159 | self.setLinearVelocity(walk_direction, True) 160 | else: 161 | if key.events[events.EKEY]: 162 | walk_direction[2] += speed 163 | if key.events[events.CKEY]: 164 | walk_direction[2] -= speed 165 | walk_direction /= logic.getAverageFrameRate() 166 | 167 | self.applyMovement(walk_direction, True) 168 | 169 | def register(cont): 170 | utilities.register(MouseLook, cont) 171 | -------------------------------------------------------------------------------- /game_engine/scripts/utilities.py: -------------------------------------------------------------------------------- 1 | import math 2 | import mathutils 3 | 4 | ANGLE_0 = .0 5 | ANGLE_1 = math.pi / 180 6 | ANGLE_90 = math.pi / 2 7 | ANGLE_180 = math.pi 8 | ANGLE_270 = math.pi * 3 / 2 9 | ANGLE_360 = math.pi * 2 10 | 11 | AXIS_X = mathutils.Vector((1, 0, 0)) 12 | AXIS_Y = mathutils.Vector((0, 1, 0)) 13 | AXIS_Z = mathutils.Vector((0, 0, 1)) 14 | 15 | VEC_0 = mathutils.Vector() 16 | 17 | 18 | def register(cls, cont): 19 | cls(cont.owner) 20 | cont.script = "scripts.utilities.main" 21 | 22 | def main(cont): 23 | cont.owner.main() 24 | -------------------------------------------------------------------------------- /game_engine/textures/colgrid512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/underdoggit2/BlenderPythonScripts/9e688b8be56f7488fd03c94dc4151c8ee34aa817/game_engine/textures/colgrid512.png -------------------------------------------------------------------------------- /gcode/evaluate_gcode.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def parse_gcode(gcode): 4 | c = bpy.data.curves.new("gcode_path", 'CURVE') 5 | c.dimensions = '3D' 6 | add_line(c, 2, (0, 0, 0), (1, 0, 0)) 7 | #for l in gcode.lines: 8 | # print(l.body) 9 | # print("---") 10 | 11 | ob = bpy.data.objects.new("gcode_path", c) 12 | bpy.context.scene.objects.link(ob) 13 | bpy.context.scene.update() 14 | 15 | def add_line(curve, num_points, *pcoords): 16 | sp = curve.splines.new('POLY') 17 | print(len(sp.points)) 18 | #sp.points.add(num_points-1) 19 | #for point, pcoord in zip(sp.points, coords): 20 | # point.co = tuple(pcoord) + (.0,) 21 | 22 | def main(): 23 | for t in bpy.data.texts: 24 | if t.name.endswith(".gcode"): 25 | parse_gcode(t) 26 | break 27 | 28 | -------------------------------------------------------------------------------- /gcode/inkscape_star_export.gcode: -------------------------------------------------------------------------------- 1 | G90 2 | G21 3 | M103 4 | M108 S210.0 5 | G1 X38.3 Y-24.0 Z10.0 F60.0 6 | G1 X38.3 Y-24.0 Z0.0 F60.0 7 | M101 8 | G1 X40.5 Y-22.5 Z-5.0 F6.8 9 | G1 X38.4 Y-20.9 Z-5.0 F960.0 10 | G1 X28.6 Y-17.0 Z-5.0 F960.0 11 | G1 X28.8 Y-6.4 Z-5.0 F960.0 12 | G1 X28.2 Y-3.8 Z-5.0 F960.0 13 | G1 X26.0 Y-5.3 Z-5.0 F960.0 14 | G1 X19.2 Y-13.4 Z-5.0 F960.0 15 | G1 X9.2 Y-9.9 Z-5.0 F960.0 16 | G1 X6.5 Y-9.7 Z-5.0 F960.0 17 | G1 X7.3 Y-12.3 Z-5.0 F960.0 18 | G1 X12.9 Y-21.3 Z-5.0 F960.0 19 | G1 X6.4 Y-29.7 Z-5.0 F960.0 20 | G1 X5.5 Y-32.1 Z-5.0 F960.0 21 | G1 X8.1 Y-32.2 Z-5.0 F960.0 22 | G1 X18.4 Y-29.6 Z-5.0 F960.0 23 | G1 X24.4 Y-38.4 Z-5.0 F960.0 24 | G1 X26.5 Y-40.1 Z-5.0 F960.0 25 | G1 X27.4 Y-37.6 Z-5.0 F960.0 26 | G1 X28.1 Y-27.0 Z-5.0 F960.0 27 | G1 X38.3 Y-24.0 Z-5.0 F960.0 28 | M103 29 | M103 30 | -------------------------------------------------------------------------------- /gcode/io_curve_gcode/__init__.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License 5 | # as published by the Free Software Foundation; either version 2 6 | # of the License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software Foundation, 15 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 | # 17 | # ##### END GPL LICENSE BLOCK ##### 18 | 19 | # 20 | 21 | bl_info = { 22 | "name": "CNC Gcode format", 23 | "author": "Hidesato Ikeya (BonBaba)", 24 | "version": (1, 0), 25 | "blender": (2, 6, 2), 26 | "location": "File > Import > CNC Gcode", 27 | "description": "Import CNC Gcode files", 28 | "warning": "", 29 | "wiki_url": "", 30 | "tracker_url": "", 31 | "category": "Import-Export"} 32 | 33 | import bpy 34 | 35 | 36 | # ImportHelper is a helper class, defines filename and 37 | # invoke() function which calls the file selector. 38 | from bpy_extras.io_utils import ImportHelper 39 | from bpy.props import StringProperty, BoolProperty, EnumProperty 40 | from bpy.types import Operator 41 | 42 | 43 | class ImportSomeData(Operator, ImportHelper): 44 | """This appears in the tooltip of the operator and in the generated docs""" 45 | bl_idname = "import_curve.gcode" # important since its how bpy.ops.import_test.some_data is constructed 46 | bl_label = "Import CNC Gcode" 47 | 48 | # ImportHelper mixin class uses this 49 | filename_ext = ".gcode" 50 | filter_glob = StringProperty( 51 | default="*.gcode", 52 | options={'HIDDEN'}, 53 | ) 54 | 55 | # List of operator properties, the attributes will be assigned 56 | # to the class instance from the operator settings before calling. 57 | use_setting = BoolProperty( 58 | name="Example Boolean", 59 | description="Example Tooltip", 60 | default=True, 61 | ) 62 | 63 | type = EnumProperty( 64 | name="Example Enum", 65 | description="Choose between two items", 66 | items=(('OPT_A', "First Option", "Description one"), 67 | ('OPT_B', "Second Option", "Description two")), 68 | default='OPT_A', 69 | ) 70 | 71 | def execute(self, context): 72 | txt = context.blend_data.texts.load(filepath) 73 | return {'FINISHED'} 74 | 75 | 76 | # Only needed if you want to add into a dynamic menu 77 | def menu_func_import(self, context): 78 | self.layout.operator(ImportSomeData.bl_idname, text="CNC gcode (.gcode)") 79 | 80 | 81 | def register(): 82 | bpy.utils.register_class(ImportSomeData) 83 | bpy.types.INFO_MT_file_import.append(menu_func_import) 84 | 85 | 86 | def unregister(): 87 | bpy.utils.unregister_class(ImportSomeData) 88 | bpy.types.INFO_MT_file_import.remove(menu_func_import) 89 | 90 | 91 | if __name__ == "__main__": 92 | register() 93 | 94 | # test call 95 | bpy.ops.import_test.some_data('INVOKE_DEFAULT') 96 | -------------------------------------------------------------------------------- /gcode/test.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/underdoggit2/BlenderPythonScripts/9e688b8be56f7488fd03c94dc4151c8ee34aa817/gcode/test.blend -------------------------------------------------------------------------------- /mesh_normalize_deform_weight.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN GPL LICENSE BLOCK ***** 2 | # 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ***** END GPL LICENCE BLOCK ***** 19 | 20 | bl_info = { 21 | "name": "Normalize Deform Weight", 22 | "author": "Hidesato Ikeya", 23 | "version": (1, 1), 24 | "blender": (2, 68, 0), 25 | "location": "View3D > ToolShelf > Normalize Deform", 26 | "description": "Normalize vertex weight to which vertices belongs" 27 | "deformed by active armature", 28 | "warning": "", 29 | "wiki_url": "", 30 | "tracker_url": "", 31 | "category": "Mesh"} 32 | 33 | import bpy 34 | 35 | class _Base: 36 | bl_options = {'REGISTER', 'UNDO'} 37 | # If total of vertex weights is under this value, 38 | # vertex weight isn't normalized. 39 | threshold = bpy.props.FloatProperty( 40 | name="threshold", default=0.000001, min=0.0000001, max=1.0, 41 | step=0.000001, precision=6) 42 | 43 | lock_active = bpy.props.BoolProperty(name="lock active", default=False) 44 | 45 | def _normalize(self, context, selected): 46 | obj = context.weight_paint_object 47 | if not context.active_pose_bone: 48 | self.report({'ERROR'}, "Active pose bone must be selected") 49 | return {'CANCELLED'} 50 | 51 | path = repr(context.active_pose_bone) 52 | # path should be "bpy.data.objects['ArmatureName'].pose.bones['BoneName']" 53 | armature_name = path[path.find("[")+2:path.find("]")-1] 54 | armature = context.scene.objects[armature_name] 55 | 56 | deform_bones = set() 57 | for bone in armature.data.bones: 58 | if bone.use_deform: 59 | deform_bones.add(bone.name) 60 | 61 | deform_groups = set() 62 | locked_deform_groups = set() 63 | for group in obj.vertex_groups: 64 | if group.name in deform_bones: 65 | if group.lock_weight: 66 | locked_deform_groups.add(group.index) 67 | elif self.lock_active and \ 68 | group.index == obj.vertex_groups.active_index: 69 | locked_deform_groups.add(group.index) 70 | else: 71 | deform_groups.add(group.index) 72 | 73 | lock_overrange = False 74 | for v in obj.data.vertices: 75 | if selected and not v.select: 76 | continue 77 | weight_total = 0 78 | locked_weight_total = 0 79 | for grp in v.groups: 80 | if grp.group in locked_deform_groups: 81 | locked_weight_total += grp.weight 82 | elif grp.group in deform_groups: 83 | weight_total += grp.weight 84 | 85 | if weight_total < self.threshold: 86 | continue 87 | 88 | factor = 1.0 - locked_weight_total 89 | if factor < 0.0: 90 | lock_overrange = True 91 | factor = 0 92 | for g in v.groups: 93 | if g.group in deform_groups: 94 | g.weight = (g.weight / weight_total) * factor 95 | 96 | if lock_overrange: 97 | self.report({'WARNING'}, "Some locked weight total is over 1.0") 98 | return {'FINISHED'} 99 | 100 | 101 | class VertexWeightNormalizeDeform(_Base, bpy.types.Operator): 102 | bl_idname = 'mesh.vertex_weight_normalize_deform' 103 | bl_label = "Normalize Deform Weight" 104 | 105 | @classmethod 106 | def poll(cls, context): 107 | return context.mode == 'PAINT_WEIGHT' 108 | def execute(self, context): 109 | return self._normalize(context, False) 110 | def invoke(self, context, event): 111 | return self.execute(context) 112 | 113 | class VertexWeightNormalizeDeformSelected(_Base, bpy.types.Operator): 114 | bl_idname = 'mesh.vertex_weight_normalize_deform_selected' 115 | bl_label = "Normalize Deform Weight Selected" 116 | 117 | @classmethod 118 | def poll(cls, context): 119 | me = context.weight_paint_object.data 120 | return context.mode == 'PAINT_WEIGHT' and \ 121 | (me.use_paint_mask or me.use_paint_mask_vertex) 122 | def execute(self, context): 123 | return self._normalize(context, True) 124 | def invoke(self, context, event): 125 | return self.execute(context) 126 | 127 | 128 | def panel_draw(self, context): 129 | layout = self.layout 130 | layout.label("Normalize Deform:") 131 | row = layout.row(align=True) 132 | row.operator('mesh.vertex_weight_normalize_deform', 133 | text="ALL") 134 | row.operator('mesh.vertex_weight_normalize_deform_selected', 135 | text="Selected") 136 | 137 | 138 | def register(): 139 | bpy.utils.register_class(VertexWeightNormalizeDeform) 140 | bpy.utils.register_class(VertexWeightNormalizeDeformSelected) 141 | bpy.types.VIEW3D_PT_tools_weightpaint.append(panel_draw) 142 | 143 | def unregister(): 144 | bpy.utils.unregister_class(VertexWeightNormalizeDeform) 145 | bpy.utils.unregister_class(VertexWeightNormalizeDeformSelected) 146 | bpy.types.VIEW3D_PT_tools_weightpaint.remove(panel_draw) 147 | 148 | if __name__ == '__main__': 149 | register() 150 | -------------------------------------------------------------------------------- /mesh_offset_edges.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN GPL LICENSE BLOCK ***** 2 | # 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ***** END GPL LICENCE BLOCK ***** 19 | 20 | bl_info = { 21 | "name": "Offset Edges", 22 | "author": "Hidesato Ikeya", 23 | "version": (0, 3, 9), 24 | "blender": (2, 76, 0), 25 | "location": "VIEW3D > Edge menu(CTRL-E) > Offset Edges", 26 | "description": "Offset Edges", 27 | "warning": "", 28 | "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Modeling/offset_edges", 29 | "tracker_url": "", 30 | "category": "Mesh"} 31 | 32 | import math 33 | from math import sin, cos, pi, copysign, radians, degrees, atan, sqrt 34 | import bpy 35 | import mathutils 36 | from bpy_extras import view3d_utils 37 | import bmesh 38 | from mathutils import Vector 39 | from time import perf_counter 40 | 41 | X_UP = Vector((1.0, .0, .0)) 42 | Y_UP = Vector((.0, 1.0, .0)) 43 | Z_UP = Vector((.0, .0, 1.0)) 44 | ZERO_VEC = Vector((.0, .0, .0)) 45 | ANGLE_1 = pi / 180 46 | ANGLE_90 = pi / 2 47 | ANGLE_180 = pi 48 | ANGLE_360 = 2 * pi 49 | 50 | class OffsetEdgesPreferences(bpy.types.AddonPreferences): 51 | bl_idname = __name__ 52 | interactive = bpy.props.BoolProperty( 53 | name = "Interactive", 54 | description = "makes operation interactive", 55 | default = True) 56 | free_move = bpy.props.BoolProperty( 57 | name = "Free Move", 58 | description = "enables to adjust both width and depth while pressing ctrl-key", 59 | default = False) 60 | 61 | def draw(self, context): 62 | layout = self.layout 63 | row = layout.row() 64 | row.prop(self, "interactive") 65 | if self.interactive: 66 | row.prop(self, "free_move") 67 | 68 | ####################################################################### 69 | 70 | class OffsetBase: 71 | follow_face = bpy.props.BoolProperty( 72 | name="Follow Face", default=False, 73 | description="Offset along faces around") 74 | mirror_modifier = bpy.props.BoolProperty( 75 | name="Mirror Modifier", default=False, 76 | description="Take into account of Mirror modifier") 77 | edge_rail = bpy.props.BoolProperty( 78 | name="Edge Rail", default=False, 79 | description="Align vertices along inner edges") 80 | edge_rail_only_end = bpy.props.BoolProperty( 81 | name="Edge Rail Only End", default=False, 82 | description="Apply edge rail to end verts only") 83 | threshold = bpy.props.FloatProperty( 84 | name="Flat Face Threshold", default=radians(0.05), precision=5, 85 | step=1.0e-4, subtype='ANGLE', 86 | description="If difference of angle between two adjacent faces is " 87 | "below this value, those faces are regarded as flat.", 88 | options={'HIDDEN'}) 89 | caches_valid = bpy.props.BoolProperty( 90 | name="Caches Valid", default=False, 91 | options={'HIDDEN'}) 92 | 93 | _cache_offset_infos = None 94 | _cache_edges_orig = None 95 | 96 | def use_caches(self, context): 97 | self.caches_valid = True 98 | 99 | def get_caches(self, bm): 100 | bmverts = tuple(bm.verts) 101 | bmedges = tuple(bm.edges) 102 | 103 | offset_infos = \ 104 | [(bmverts[vix], co, d) for vix, co, d in self._cache_offset_infos] 105 | edges_orig = [bmedges[eix] for eix in self._cache_edges_orig] 106 | 107 | for e in edges_orig: 108 | e.select = False 109 | for f in bm.faces: 110 | f.select = False 111 | 112 | return offset_infos, edges_orig 113 | 114 | def save_caches(self, offset_infos, edges_orig): 115 | self._cache_offset_infos = tuple((v.index, co, d) for v, co, d in offset_infos) 116 | self._cache_edges_orig = tuple(e.index for e in edges_orig) 117 | 118 | @staticmethod 119 | def is_face_selected(ob_edit): 120 | bpy.ops.object.mode_set(mode="OBJECT") 121 | me = ob_edit.data 122 | for p in me.polygons: 123 | if p.select: 124 | bpy.ops.object.mode_set(mode="EDIT") 125 | return True 126 | bpy.ops.object.mode_set(mode="EDIT") 127 | 128 | return False 129 | @staticmethod 130 | def is_mirrored(ob_edit): 131 | for mod in ob_edit.modifiers: 132 | if mod.type == 'MIRROR' and mod.use_mirror_merge: 133 | return True 134 | return False 135 | 136 | @staticmethod 137 | def reorder_loop(verts, edges, lp_normal, adj_faces): 138 | for i, adj_f in enumerate(adj_faces): 139 | if adj_f is None: 140 | continue 141 | v1, v2 = verts[i], verts[i+1] 142 | e = edges[i] 143 | fv = tuple(adj_f.verts) 144 | if fv[fv.index(v1)-1] is v2: 145 | # Align loop direction 146 | verts.reverse() 147 | edges.reverse() 148 | adj_faces.reverse() 149 | if lp_normal.dot(adj_f.normal) < .0: 150 | lp_normal *= -1 151 | break 152 | else: 153 | # All elements in adj_faces are None 154 | for v in verts: 155 | if v.normal != ZERO_VEC: 156 | if lp_normal.dot(v.normal) < .0: 157 | verts.reverse() 158 | edges.reverse() 159 | lp_normal *= -1 160 | break 161 | 162 | return verts, edges, lp_normal, adj_faces 163 | 164 | @staticmethod 165 | def get_cross_rail(vec_tan, vec_edge_r, vec_edge_l, normal_r, normal_l): 166 | # Cross rail is a cross vector between normal_r and normal_l. 167 | 168 | vec_cross = normal_r.cross(normal_l) 169 | if vec_cross.dot(vec_tan) < .0: 170 | vec_cross *= -1 171 | cos_min = min(vec_tan.dot(vec_edge_r), vec_tan.dot(-vec_edge_l)) 172 | cos = vec_tan.dot(vec_cross) 173 | if cos >= cos_min: 174 | vec_cross.normalize() 175 | return vec_cross 176 | else: 177 | return None 178 | 179 | @staticmethod 180 | def get_edge_rail(vert, set_edges_orig): 181 | co_edges = co_edges_selected = 0 182 | vec_inner = None 183 | for e in vert.link_edges: 184 | if (e not in set_edges_orig and 185 | (e.select or (co_edges_selected == 0 and not e.hide))): 186 | v_other = e.other_vert(vert) 187 | vec = v_other.co - vert.co 188 | if vec != ZERO_VEC: 189 | vec_inner = vec 190 | if e.select: 191 | co_edges_selected += 1 192 | if co_edges_selected == 2: 193 | return None 194 | else: 195 | co_edges += 1 196 | if co_edges_selected == 1: 197 | vec_inner.normalize() 198 | return vec_inner 199 | elif co_edges == 1: 200 | # No selected edges, one unselected edge. 201 | vec_inner.normalize() 202 | return vec_inner 203 | else: 204 | return None 205 | 206 | @staticmethod 207 | def get_mirror_rail(mirror_plane, vec_up): 208 | p_norm = mirror_plane[1] 209 | mirror_rail = vec_up.cross(p_norm) 210 | if mirror_rail != ZERO_VEC: 211 | mirror_rail.normalize() 212 | # Project vec_up to mirror_plane 213 | vec_up = vec_up - vec_up.project(p_norm) 214 | vec_up.normalize() 215 | return mirror_rail, vec_up 216 | else: 217 | return None, vec_up 218 | 219 | @staticmethod 220 | def get_vert_mirror_pairs(set_edges_orig, mirror_planes): 221 | if mirror_planes: 222 | set_edges_copy = set_edges_orig.copy() 223 | vert_mirror_pairs = dict() 224 | for e in set_edges_orig: 225 | v1, v2 = e.verts 226 | for mp in mirror_planes: 227 | p_co, p_norm, mlimit = mp 228 | v1_dist = abs(p_norm.dot(v1.co - p_co)) 229 | v2_dist = abs(p_norm.dot(v2.co - p_co)) 230 | if v1_dist <= mlimit: 231 | # v1 is on a mirror plane. 232 | vert_mirror_pairs[v1] = mp 233 | if v2_dist <= mlimit: 234 | # v2 is on a mirror plane. 235 | vert_mirror_pairs[v2] = mp 236 | if v1_dist <= mlimit and v2_dist <= mlimit: 237 | # This edge is on a mirror_plane, so should not be offsetted. 238 | set_edges_copy.remove(e) 239 | return vert_mirror_pairs, set_edges_copy 240 | else: 241 | return None, set_edges_orig 242 | 243 | @staticmethod 244 | def collect_mirror_planes(ob_edit): 245 | mirror_planes = [] 246 | eob_mat_inv = ob_edit.matrix_world.inverted() 247 | for m in ob_edit.modifiers: 248 | if (m.type == 'MIRROR' and m.use_mirror_merge): 249 | merge_limit = m.merge_threshold 250 | if not m.mirror_object: 251 | loc = ZERO_VEC 252 | norm_x, norm_y, norm_z = X_UP, Y_UP, Z_UP 253 | else: 254 | mirror_mat_local = eob_mat_inv * m.mirror_object.matrix_world 255 | loc = mirror_mat_local.to_translation() 256 | norm_x, norm_y, norm_z, _ = mirror_mat_local.adjugated() 257 | norm_x = norm_x.to_3d().normalized() 258 | norm_y = norm_y.to_3d().normalized() 259 | norm_z = norm_z.to_3d().normalized() 260 | if m.use_x: 261 | mirror_planes.append((loc, norm_x, merge_limit)) 262 | if m.use_y: 263 | mirror_planes.append((loc, norm_y, merge_limit)) 264 | if m.use_z: 265 | mirror_planes.append((loc, norm_z, merge_limit)) 266 | return mirror_planes 267 | 268 | @staticmethod 269 | def collect_edges(bm): 270 | set_edges_orig = set() 271 | for e in bm.edges: 272 | if e.select: 273 | co_faces_selected = 0 274 | for f in e.link_faces: 275 | if f.select: 276 | co_faces_selected += 1 277 | if co_faces_selected == 2: 278 | break 279 | else: 280 | set_edges_orig.add(e) 281 | 282 | if not set_edges_orig: 283 | return None 284 | 285 | return set_edges_orig 286 | @staticmethod 287 | def collect_loops(set_edges_orig): 288 | set_edges_copy = set_edges_orig.copy() 289 | 290 | loops = [] # [v, e, v, e, ... , e, v] 291 | while set_edges_copy: 292 | edge_start = set_edges_copy.pop() 293 | v_left, v_right = edge_start.verts 294 | lp = [v_left, edge_start, v_right] 295 | reverse = False 296 | while True: 297 | edge = None 298 | for e in v_right.link_edges: 299 | if e in set_edges_copy: 300 | if edge: 301 | # Overlap detected. 302 | return None 303 | edge = e 304 | set_edges_copy.remove(e) 305 | if edge: 306 | v_right = edge.other_vert(v_right) 307 | lp.extend((edge, v_right)) 308 | continue 309 | else: 310 | if v_right is v_left: 311 | # Real loop. 312 | loops.append(lp) 313 | break 314 | elif reverse is False: 315 | # Right side of half loop. 316 | # Reversing the loop to operate same procedure on the left side. 317 | lp.reverse() 318 | v_right, v_left = v_left, v_right 319 | reverse = True 320 | continue 321 | else: 322 | # Half loop, completed. 323 | loops.append(lp) 324 | break 325 | return loops 326 | 327 | @staticmethod 328 | def calc_loop_normal(verts, fallback=Z_UP): 329 | # Calculate normal from verts using Newell's method. 330 | normal = ZERO_VEC.copy() 331 | 332 | if verts[0] is verts[-1]: 333 | # Perfect loop 334 | range_verts = range(1, len(verts)) 335 | else: 336 | # Half loop 337 | range_verts = range(0, len(verts)) 338 | 339 | for i in range_verts: 340 | v1co, v2co = verts[i-1].co, verts[i].co 341 | normal.x += (v1co.y - v2co.y) * (v1co.z + v2co.z) 342 | normal.y += (v1co.z - v2co.z) * (v1co.x + v2co.x) 343 | normal.z += (v1co.x - v2co.x) * (v1co.y + v2co.y) 344 | 345 | if normal != ZERO_VEC: 346 | normal.normalize() 347 | else: 348 | normal = fallback 349 | 350 | return normal 351 | 352 | @staticmethod 353 | def get_adj_faces(edges): 354 | adj_faces = [] 355 | for e in edges: 356 | adj_f = None 357 | co_adj = 0 358 | for f in e.link_faces: 359 | # Search an adjacent face. 360 | # Selected face has precedance. 361 | if not f.hide and f.normal != ZERO_VEC: 362 | adj_exist = True 363 | adj_f = f 364 | co_adj += 1 365 | if f.select: 366 | adj_faces.append(adj_f) 367 | break 368 | else: 369 | if co_adj == 1: 370 | adj_faces.append(adj_f) 371 | else: 372 | adj_faces.append(None) 373 | return adj_faces 374 | 375 | def get_directions(self, lp, vec_upward, normal_fallback, vert_mirror_pairs): 376 | opt_follow_face = self.follow_face 377 | opt_edge_rail = self.edge_rail 378 | opt_er_only_end = self.edge_rail_only_end 379 | opt_threshold = self.threshold 380 | 381 | verts, edges = lp[::2], lp[1::2] 382 | set_edges = set(edges) 383 | lp_normal = self.calc_loop_normal(verts, fallback=normal_fallback) 384 | 385 | ##### Loop order might be changed below. 386 | if lp_normal.dot(vec_upward) < .0: 387 | # Make this loop's normal towards vec_upward. 388 | verts.reverse() 389 | edges.reverse() 390 | lp_normal *= -1 391 | 392 | if opt_follow_face: 393 | adj_faces = self.get_adj_faces(edges) 394 | verts, edges, lp_normal, adj_faces = \ 395 | self.reorder_loop(verts, edges, lp_normal, adj_faces) 396 | else: 397 | adj_faces = (None, ) * len(edges) 398 | ##### Loop order might be changed above. 399 | 400 | vec_edges = tuple((e.other_vert(v).co - v.co).normalized() 401 | for v, e in zip(verts, edges)) 402 | 403 | if verts[0] is verts[-1]: 404 | # Real loop. Popping last vertex. 405 | verts.pop() 406 | HALF_LOOP = False 407 | else: 408 | # Half loop 409 | HALF_LOOP = True 410 | 411 | len_verts = len(verts) 412 | directions = [] 413 | for i in range(len_verts): 414 | vert = verts[i] 415 | ix_right, ix_left = i, i-1 416 | 417 | VERT_END = False 418 | if HALF_LOOP: 419 | if i == 0: 420 | # First vert 421 | ix_left = ix_right 422 | VERT_END = True 423 | elif i == len_verts - 1: 424 | # Last vert 425 | ix_right = ix_left 426 | VERT_END = True 427 | 428 | edge_right, edge_left = vec_edges[ix_right], vec_edges[ix_left] 429 | face_right, face_left = adj_faces[ix_right], adj_faces[ix_left] 430 | 431 | norm_right = face_right.normal if face_right else lp_normal 432 | norm_left = face_left.normal if face_left else lp_normal 433 | if norm_right.angle(norm_left) > opt_threshold: 434 | # Two faces are not flat. 435 | two_normals = True 436 | else: 437 | two_normals = False 438 | 439 | tan_right = edge_right.cross(norm_right).normalized() 440 | tan_left = edge_left.cross(norm_left).normalized() 441 | tan_avr = (tan_right + tan_left).normalized() 442 | norm_avr = (norm_right + norm_left).normalized() 443 | 444 | rail = None 445 | if two_normals or opt_edge_rail: 446 | # Get edge rail. 447 | # edge rail is a vector of an inner edge. 448 | if two_normals or (not opt_er_only_end) or VERT_END: 449 | rail = self.get_edge_rail(vert, set_edges) 450 | if vert_mirror_pairs and VERT_END: 451 | if vert in vert_mirror_pairs: 452 | rail, norm_avr = \ 453 | self.get_mirror_rail(vert_mirror_pairs[vert], norm_avr) 454 | if (not rail) and two_normals: 455 | # Get cross rail. 456 | # Cross rail is a cross vector between norm_right and norm_left. 457 | rail = self.get_cross_rail( 458 | tan_avr, edge_right, edge_left, norm_right, norm_left) 459 | if rail: 460 | dot = tan_avr.dot(rail) 461 | if dot > .0: 462 | tan_avr = rail 463 | elif dot < .0: 464 | tan_avr = -rail 465 | 466 | vec_plane = norm_avr.cross(tan_avr) 467 | e_dot_p_r = edge_right.dot(vec_plane) 468 | e_dot_p_l = edge_left.dot(vec_plane) 469 | if e_dot_p_r or e_dot_p_l: 470 | if e_dot_p_r > e_dot_p_l: 471 | vec_edge, e_dot_p = edge_right, e_dot_p_r 472 | else: 473 | vec_edge, e_dot_p = edge_left, e_dot_p_l 474 | 475 | vec_tan = (tan_avr - tan_avr.project(vec_edge)).normalized() 476 | # Make vec_tan perpendicular to vec_edge 477 | vec_up = vec_tan.cross(vec_edge) 478 | 479 | vec_width = vec_tan - (vec_tan.dot(vec_plane) / e_dot_p) * vec_edge 480 | vec_depth = vec_up - (vec_up.dot(vec_plane) / e_dot_p) * vec_edge 481 | else: 482 | vec_width = tan_avr 483 | vec_depth = norm_avr 484 | 485 | directions.append((vec_width, vec_depth)) 486 | 487 | return verts, directions 488 | 489 | def get_offset_infos(self, bm, ob_edit): 490 | time = perf_counter() 491 | 492 | set_edges_orig = self.collect_edges(bm) 493 | if set_edges_orig is None: 494 | self.report({'WARNING'}, 495 | "No edges are selected.") 496 | return False, False 497 | 498 | if self.mirror_modifier: 499 | mirror_planes = self.collect_mirror_planes(ob_edit) 500 | vert_mirror_pairs, set_edges = \ 501 | self.get_vert_mirror_pairs(set_edges_orig, mirror_planes) 502 | 503 | if set_edges: 504 | set_edges_orig = set_edges 505 | else: 506 | #self.report({'WARNING'}, 507 | # "All selected edges are on mirror planes.") 508 | vert_mirror_pairs = None 509 | else: 510 | vert_mirror_pairs = None 511 | edges_orig = list(set_edges_orig) 512 | 513 | loops = self.collect_loops(set_edges_orig) 514 | if loops is None: 515 | self.report({'WARNING'}, 516 | "Overlapping edge loops detected. Select discrete edge loops") 517 | return False, False 518 | 519 | vec_upward = (X_UP + Y_UP + Z_UP).normalized() 520 | # vec_upward is used to unify loop normals when follow_face is off. 521 | normal_fallback = Z_UP 522 | #normal_fallback = Vector(context.region_data.view_matrix[2][:3]) 523 | # normal_fallback is used when loop normal cannot be calculated. 524 | 525 | offset_infos = [] 526 | for lp in loops: 527 | verts, directions = self.get_directions( 528 | lp, vec_upward, normal_fallback, vert_mirror_pairs) 529 | if verts: 530 | # convert vert objects to vert indexs 531 | for v, d in zip(verts, directions): 532 | offset_infos.append((v, v.co.copy(), d)) 533 | 534 | for e in edges_orig: 535 | e.select = False 536 | for f in bm.faces: 537 | f.select = False 538 | 539 | print("Preparing OffsetEdges: ", perf_counter() - time) 540 | 541 | return offset_infos, edges_orig 542 | 543 | @staticmethod 544 | def extrude_and_pairing(bm, edges_orig, ref_verts): 545 | """ ref_verts is a list of vertices, each of which should be 546 | one end of an edge in edges_orig""" 547 | extruded = bmesh.ops.extrude_edge_only(bm, edges=edges_orig)['geom'] 548 | n_edges = n_faces = len(edges_orig) 549 | n_verts = len(extruded) - n_edges - n_faces 550 | 551 | exverts = set(extruded[:n_verts]) 552 | exedges = set(extruded[n_verts:n_verts + n_edges]) 553 | #faces = set(extruded[n_verts + n_edges:]) 554 | side_edges = set(e for v in exverts for e in v.link_edges if e not in exedges) 555 | 556 | # ref_verts[i] and ret[i] are both ends of a side edge. 557 | exverts_ordered = \ 558 | [e.other_vert(v) for v in ref_verts for e in v.link_edges if e in side_edges] 559 | 560 | return exverts_ordered, list(exedges), list(side_edges) 561 | 562 | @staticmethod 563 | def move_verts(bm, me, width, depth, offset_infos, verts_offset=None, update=True): 564 | if verts_offset is None: 565 | for v, co, (vec_w, vec_d) in offset_infos: 566 | v.co = co + width * vec_w + depth * vec_d 567 | else: 568 | for (_, co, (vec_w, vec_d)), v in zip(offset_infos, verts_offset): 569 | v.co = co + width * vec_w + depth * vec_d 570 | 571 | if update: 572 | bm.normal_update() 573 | bmesh.update_edit_mesh(me) 574 | 575 | 576 | class OffsetEdges(bpy.types.Operator, OffsetBase): 577 | """Offset Edges.""" 578 | bl_idname = "mesh.offset_edges" 579 | bl_label = "Offset Edges" 580 | bl_options = {'REGISTER', 'UNDO'} 581 | 582 | # Functions below are update functions 583 | 584 | def assign_angle_presets(self, context): 585 | angle_presets = {'0°': 0, 586 | '15°': radians(15), 587 | '30°': radians(30), 588 | '45°': radians(45), 589 | '60°': radians(60), 590 | '75°': radians(75), 591 | '90°': radians(90),} 592 | self.angle = angle_presets[self.angle_presets] 593 | 594 | def change_depth_mode(self, context): 595 | if self.depth_mode == 'angle': 596 | self.width, self.angle = OffsetEdges.depth_to_angle(self.width, self.depth) 597 | else: 598 | self.width, self.depth = OffsetEdges.angle_to_depth(self.width, self.angle) 599 | 600 | 601 | def angle_to_depth(width, angle): 602 | """Returns: (converted_width, converted_depth)""" 603 | return width * cos(angle), width * sin(angle) 604 | 605 | 606 | def depth_to_angle(width, depth): 607 | """Returns: (converted_width, converted_angle)""" 608 | ret_width = sqrt(width * width + depth * depth) 609 | 610 | if width: 611 | ret_angle = atan(depth / width) 612 | elif depth == 0: 613 | ret_angle = 0 614 | elif depth > 0: 615 | ret_angle = ANGLE_90 616 | elif depth < 0: 617 | ret_angle = -ANGLE_90 618 | 619 | return ret_width, ret_angle 620 | 621 | geometry_mode = bpy.props.EnumProperty( 622 | items=[('offset', "Offset", "Offset edges"), 623 | ('extrude', "Extrude", "Extrude edges"), 624 | ('move', "Move", "Move selected edges")], 625 | name="Geometory mode", default='offset', 626 | update=OffsetBase.use_caches) 627 | width = bpy.props.FloatProperty( 628 | name="Width", default=.2, precision=4, step=1, 629 | update=OffsetBase.use_caches) 630 | flip_width = bpy.props.BoolProperty( 631 | name="Flip Width", default=False, 632 | description="Flip width direction", 633 | update=OffsetBase.use_caches) 634 | depth = bpy.props.FloatProperty( 635 | name="Depth", default=.0, precision=4, step=1, 636 | update=OffsetBase.use_caches) 637 | flip_depth = bpy.props.BoolProperty( 638 | name="Flip Depth", default=False, 639 | description="Flip depth direction", 640 | update=OffsetBase.use_caches) 641 | depth_mode = bpy.props.EnumProperty( 642 | items=[('angle', "Angle", "Angle"), 643 | ('depth', "Depth", "Depth")], 644 | name="Depth mode", default='angle', 645 | update=change_depth_mode) 646 | angle = bpy.props.FloatProperty( 647 | name="Angle", default=0, precision=3, step=100, 648 | min=-2*pi, max=2*pi, subtype='ANGLE', description="Angle", 649 | update=OffsetBase.use_caches) 650 | flip_angle = bpy.props.BoolProperty( 651 | name="Flip Angle", default=False, 652 | description="Flip Angle", 653 | update=OffsetBase.use_caches) 654 | angle_presets = bpy.props.EnumProperty( 655 | items=[('0°', "0°", "0°"), 656 | ('15°', "15°", "15°"), 657 | ('30°', "30°", "30°"), 658 | ('45°', "45°", "45°"), 659 | ('60°', "60°", "60°"), 660 | ('75°', "75°", "75°"), 661 | ('90°', "90°", "90°"), ], 662 | name="Angle Presets", default='0°', 663 | update=assign_angle_presets) 664 | 665 | 666 | def get_exverts(self, bm, offset_infos, edges_orig): 667 | ref_verts = [v for v, _, _ in offset_infos] 668 | 669 | if self.geometry_mode == 'move': 670 | exverts = ref_verts 671 | exedges = edges_orig 672 | else: 673 | exverts, exedges, side_edges = self.extrude_and_pairing(bm, edges_orig, ref_verts) 674 | if self.geometry_mode == 'offset': 675 | bmesh.ops.delete(bm, geom=side_edges, context=2) 676 | 677 | for e in exedges: 678 | e.select = True 679 | 680 | return exverts 681 | 682 | def do_offset(self, bm, me, offset_infos, verts_offset): 683 | if self.depth_mode == 'angle': 684 | w = self.width if not self.flip_width else -self.width 685 | angle = self.angle if not self.flip_angle else -self.angle 686 | width = w * cos(angle) 687 | depth = w * sin(angle) 688 | else: 689 | width = self.width if not self.flip_width else -self.width 690 | depth = self.depth if not self.flip_depth else -self.depth 691 | 692 | self.move_verts(bm, me, width, depth, offset_infos, verts_offset) 693 | 694 | @classmethod 695 | def poll(self, context): 696 | return context.mode == 'EDIT_MESH' 697 | 698 | def draw(self, context): 699 | layout = self.layout 700 | layout.prop(self, 'geometry_mode', text="") 701 | 702 | row = layout.row(align=True) 703 | row.prop(self, 'width') 704 | row.prop(self, 'flip_width', icon='ARROW_LEFTRIGHT', icon_only=True) 705 | 706 | layout.prop(self, 'depth_mode', expand=True) 707 | if self.depth_mode == 'angle': 708 | d_mode = 'angle' 709 | flip = 'flip_angle' 710 | else: 711 | d_mode = 'depth' 712 | flip = 'flip_depth' 713 | row = layout.row(align=True) 714 | row.prop(self, d_mode) 715 | row.prop(self, flip, icon='ARROW_LEFTRIGHT', icon_only=True) 716 | if self.depth_mode == 'angle': 717 | layout.prop(self, 'angle_presets', text="Presets", expand=True) 718 | 719 | layout.separator() 720 | 721 | layout.prop(self, 'follow_face') 722 | 723 | row = layout.row() 724 | row.prop(self, 'edge_rail') 725 | if self.edge_rail: 726 | row.prop(self, 'edge_rail_only_end', text="OnlyEnd", toggle=True) 727 | 728 | layout.prop(self, 'mirror_modifier') 729 | 730 | #layout.operator('mesh.offset_edges', text='Repeat') 731 | 732 | if self.follow_face: 733 | layout.separator() 734 | layout.prop(self, 'threshold', text='Threshold') 735 | 736 | 737 | def execute(self, context): 738 | # In edit mode 739 | edit_object = context.edit_object 740 | me = edit_object.data 741 | bm = bmesh.from_edit_mesh(me) 742 | 743 | if self.caches_valid and self._cache_offset_infos: 744 | offset_infos, edges_orig = self.get_caches(bm) 745 | else: 746 | offset_infos, edges_orig = self.get_offset_infos(bm, edit_object) 747 | if offset_infos is False: 748 | return {'CANCELLED'} 749 | self.save_caches(offset_infos, edges_orig) 750 | 751 | exverts = self.get_exverts(bm, offset_infos, edges_orig) 752 | self.do_offset(bm, me, offset_infos, exverts) 753 | 754 | self.caches_valid = False 755 | return {'FINISHED'} 756 | 757 | def invoke(self, context, event): 758 | # in edit mode 759 | ob_edit = context.edit_object 760 | if self.is_face_selected(ob_edit): 761 | self.follow_face = True 762 | if self.is_mirrored(ob_edit): 763 | self.mirror_modifier = True 764 | 765 | me = ob_edit.data 766 | 767 | pref = \ 768 | context.user_preferences.addons[__name__].preferences 769 | if pref.interactive and context.space_data.type == 'VIEW_3D': 770 | # interactive mode 771 | if pref.free_move: 772 | self.depth_mode = 'depth' 773 | 774 | ret = self.modal_prepare_bmeshes(context, ob_edit) 775 | if ret is False: 776 | return {'CANCELLED'} 777 | 778 | self.width = self.angle = self.depth = .0 779 | self.flip_depth = self.flip_angle = self.flip_width = False 780 | self._mouse_init = self._mouse_prev = \ 781 | Vector((event.mouse_x, event.mouse_y)) 782 | context.window_manager.modal_handler_add(self) 783 | 784 | self._factor = self.get_factor(context, self._edges_orig) 785 | 786 | # toggle switchs of keys 787 | self._F = 0 788 | self._A = 0 789 | 790 | return {'RUNNING_MODAL'} 791 | else: 792 | return self.execute(context) 793 | 794 | def modal(self, context, event): 795 | # In edit mode 796 | ob_edit = context.edit_object 797 | me = ob_edit.data 798 | pref = \ 799 | context.user_preferences.addons[__name__].preferences 800 | 801 | if event.type == 'F': 802 | # toggle follow_face 803 | # event.type == 'F' is True both when 'F' is pressed and when released, 804 | # so these codes should be executed every other loop. 805 | self._F = 1 - self._F 806 | if self._F: 807 | self.follow_face = 1 - self.follow_face 808 | 809 | self.modal_clean_bmeshes(context, ob_edit) 810 | ret = self.modal_prepare_bmeshes(context, ob_edit) 811 | if ret: 812 | self.do_offset(self._bm, me, self._offset_infos, self._exverts) 813 | return {'RUNNING_MODAL'} 814 | else: 815 | return {'CANCELLED'} 816 | 817 | if event.type == 'A': 818 | # toggle depth_mode 819 | self._A = 1 - self._A 820 | if self._A: 821 | if self.depth_mode == 'angle': 822 | self.depth_mode = 'depth' 823 | else: 824 | self.depth_mode = 'angle' 825 | 826 | context.area.header_text_set(self.create_header()) 827 | 828 | if event.type == 'MOUSEMOVE': 829 | _mouse_current = Vector((event.mouse_x, event.mouse_y)) 830 | vec_delta = _mouse_current - self._mouse_prev 831 | 832 | if pref.free_move or not event.ctrl: 833 | self.width += vec_delta.x * self._factor 834 | 835 | if event.ctrl: 836 | if self.depth_mode == 'angle': 837 | self.angle += vec_delta.y * ANGLE_1 838 | elif self.depth_mode == 'depth': 839 | self.depth += vec_delta.y * self._factor 840 | 841 | self._mouse_prev = _mouse_current 842 | 843 | self.do_offset(self._bm, me, self._offset_infos, self._exverts) 844 | return {'RUNNING_MODAL'} 845 | 846 | elif event.type == 'LEFTMOUSE': 847 | self._bm_orig.free() 848 | context.area.header_text_set() 849 | return {'FINISHED'} 850 | 851 | elif event.type in {'RIGHTMOUSE', 'ESC'}: 852 | self.modal_clean_bmeshes(context, ob_edit) 853 | context.area.header_text_set() 854 | return {'CANCELLED'} 855 | 856 | return {'RUNNING_MODAL'} 857 | 858 | # methods below are usded in interactive mode 859 | def create_header(self): 860 | header = "".join( 861 | ["Width {width: .4} ", 862 | "Depth {depth: .4}('A' to Angle) " if self.depth_mode == 'depth' else "Angle {angle: 4.0F}°('A' to Depth) ", 863 | "FollowFace(F):", 864 | "(ON)" if self.follow_face else "(OFF)", 865 | ]) 866 | 867 | return header.format(width=self.width, depth=self.depth, angle=degrees(self.angle)) 868 | 869 | def modal_prepare_bmeshes(self, context, ob_edit): 870 | bpy.ops.object.mode_set(mode="OBJECT") 871 | self._bm_orig = bmesh.new() 872 | self._bm_orig.from_mesh(ob_edit.data) 873 | bpy.ops.object.mode_set(mode="EDIT") 874 | 875 | self._bm = bmesh.from_edit_mesh(ob_edit.data) 876 | 877 | self._offset_infos, self._edges_orig = \ 878 | self.get_offset_infos(self._bm, ob_edit) 879 | if self._offset_infos is False: 880 | return False 881 | self._exverts = \ 882 | self.get_exverts(self._bm, self._offset_infos, self._edges_orig) 883 | 884 | return True 885 | 886 | def modal_clean_bmeshes(self, context, ob_edit): 887 | bpy.ops.object.mode_set(mode="OBJECT") 888 | self._bm_orig.to_mesh(ob_edit.data) 889 | bpy.ops.object.mode_set(mode="EDIT") 890 | self._bm_orig.free() 891 | self._bm.free() 892 | 893 | def get_factor(self, context, edges_orig): 894 | """get the length in the space of edited object 895 | which correspond to 1px of 3d view. This method 896 | is used to convert the distance of mouse movement 897 | to offsetting width in interactive mode. 898 | """ 899 | ob = context.edit_object 900 | mat_w = ob.matrix_world 901 | reg = context.region 902 | reg3d = context.space_data.region_3d # Don't use context.region_data 903 | # because this will cause error 904 | # when invoked from header menu. 905 | 906 | co_median = Vector((0, 0, 0)) 907 | for e in edges_orig: 908 | co_median += e.verts[0].co 909 | co_median /= len(edges_orig) 910 | depth_loc = mat_w * co_median # World coords of median point 911 | 912 | win_left = Vector((0, 0)) 913 | win_right = Vector((reg.width, 0)) 914 | left = view3d_utils.region_2d_to_location_3d(reg, reg3d, win_left, depth_loc) 915 | right = view3d_utils.region_2d_to_location_3d(reg, reg3d, win_right, depth_loc) 916 | vec_width = mat_w.inverted_safe() * (right - left) # width vector in the object space 917 | width_3d = vec_width.length # window width in the object space 918 | 919 | return width_3d / reg.width 920 | 921 | class OffsetEdgesProfile(bpy.types.Operator, OffsetBase): 922 | """Offset Edges using a profile curve.""" 923 | bl_idname = "mesh.offset_edges_profile" 924 | bl_label = "Offset Edges Profile" 925 | bl_options = {'REGISTER', 'UNDO'} 926 | 927 | res_profile = bpy.props.IntProperty( 928 | name="Resolution", default =6, min=0, max=100, 929 | update=OffsetBase.use_caches) 930 | magni_w = bpy.props.FloatProperty( 931 | name="Magnification of Width", default=1., precision=4, step=1, 932 | update=OffsetBase.use_caches) 933 | magni_d = bpy.props.FloatProperty( 934 | name="Magniofication of Depth", default=1., precision=4, step=1, 935 | update=OffsetBase.use_caches) 936 | name_profile = bpy.props.StringProperty(update=OffsetBase.use_caches) 937 | 938 | @classmethod 939 | def poll(self, context): 940 | return context.mode == 'EDIT_MESH' 941 | 942 | def draw(self, context): 943 | layout = self.layout 944 | 945 | layout.prop_search(self, 'name_profile', context.scene, 'objects', text="Profile") 946 | layout.separator() 947 | 948 | layout.prop(self, 'res_profile') 949 | 950 | row = layout.row() 951 | row.prop(self, 'magni_w', text="Width") 952 | row.prop(self, 'magni_d', text="Depth") 953 | 954 | layout.separator() 955 | layout.prop(self, 'follow_face') 956 | 957 | row = layout.row() 958 | row.prop(self, 'edge_rail') 959 | if self.edge_rail: 960 | row.prop(self, 'edge_rail_only_end', text="OnlyEnd", toggle=True) 961 | 962 | layout.prop(self, 'mirror_modifier') 963 | 964 | #layout.operator('mesh.offset_edges', text='Repeat') 965 | 966 | if self.follow_face: 967 | layout.separator() 968 | layout.prop(self, 'threshold', text='Threshold') 969 | 970 | @staticmethod 971 | def analize_profile(context, ob_profile, resolution): 972 | curve = ob_profile.data 973 | res_orig = curve.resolution_u 974 | curve.resolution_u = resolution 975 | me = ob_profile.to_mesh(context.scene, False, 'PREVIEW') 976 | curve.resolution_u = res_orig 977 | 978 | vco_start = me.vertices[0].co 979 | info_profile = [v.co - vco_start for v in me.vertices[1:]] 980 | bpy.data.meshes.remove(me) 981 | 982 | return info_profile 983 | 984 | @staticmethod 985 | def get_profile(context): 986 | ob_edit = context.edit_object 987 | for ob in context.selected_objects: 988 | if ob != ob_edit and ob.type == 'CURVE': 989 | return ob 990 | else: 991 | self.report({'WARNING'}, 992 | "Profile curve is not selected.") 993 | return None 994 | 995 | def offset_profile(self, ob_edit, info_profile): 996 | me = ob_edit.data 997 | bm = bmesh.from_edit_mesh(me) 998 | 999 | if self.caches_valid and self._cache_offset_infos: 1000 | offset_infos, edges_orig = self.get_caches(bm) 1001 | else: 1002 | offset_infos, edges_orig = self.get_offset_infos(bm, ob_edit) 1003 | if offset_infos is False: 1004 | return {'CANCELLED'} 1005 | self.save_caches(offset_infos, edges_orig) 1006 | 1007 | ref_verts = [v for v, _, _ in offset_infos] 1008 | edges = edges_orig 1009 | for width, depth, _ in info_profile: 1010 | exverts, exedges, _ = self.extrude_and_pairing(bm, edges, ref_verts) 1011 | self.move_verts( 1012 | bm, me, width * self.magni_w, 1013 | depth * self.magni_d, offset_infos, 1014 | exverts, update=False 1015 | ) 1016 | ref_verts = exverts 1017 | edges = exedges 1018 | 1019 | bm.normal_update() 1020 | bmesh.update_edit_mesh(me) 1021 | 1022 | self.caches_valid = False 1023 | 1024 | return {'FINISHED'} 1025 | 1026 | @staticmethod 1027 | def get_profile(context): 1028 | ob_edit = context.edit_object 1029 | for ob in context.selected_objects: 1030 | if ob != ob_edit and ob.type == 'CURVE': 1031 | return ob 1032 | return None 1033 | 1034 | def execute(self, context): 1035 | if not self.name_profile: 1036 | self.report({'WARNING'}, 1037 | "Select a curve object as profile.") 1038 | return {'FINISHED'} 1039 | 1040 | ob_profile = context.scene.objects[self.name_profile] 1041 | if ob_profile and ob_profile.type == "CURVE": 1042 | info_profile = self.analize_profile( 1043 | context, ob_profile, self.res_profile 1044 | ) 1045 | return self.offset_profile(context.edit_object, info_profile) 1046 | else: 1047 | self.name_profile = "" 1048 | self.report({'WARNING'}, 1049 | "Select a curve object as profile.") 1050 | return {'FINISHED'} 1051 | 1052 | def invoke(self, context, event): 1053 | ob_edit = context.edit_object 1054 | if self.is_face_selected(ob_edit): 1055 | self.follow_face = True 1056 | if self.is_mirrored(ob_edit): 1057 | self.mirror_modifier = True 1058 | 1059 | ob_profile = self.get_profile(context) 1060 | if ob_profile is None: 1061 | self.report({'WARNING'}, 1062 | "Profile curve is not selected.") 1063 | return {'CANCELLED'} 1064 | 1065 | self.name_profile = ob_profile.name 1066 | self.res_profile = ob_profile.data.resolution_u 1067 | return self.execute(context) 1068 | 1069 | class OffsetEdgesMenu(bpy.types.Menu): 1070 | bl_idname = "VIEW3D_MT_edit_mesh_offset_edges" 1071 | bl_label = "Offset Edges" 1072 | 1073 | def draw(self, context): 1074 | layout = self.layout 1075 | layout.operator_context = 'INVOKE_DEFAULT' 1076 | 1077 | self.layout.operator_enum('mesh.offset_edges', 'geometry_mode') 1078 | 1079 | layout.separator() 1080 | layout.operator('mesh.offset_edges_profile', text='Profile') 1081 | 1082 | class VIEW3D_PT_OffsetEdges(bpy.types.Panel): 1083 | bl_space_type = 'VIEW_3D' 1084 | bl_region_type = 'TOOLS' 1085 | bl_category = 'Tools' 1086 | bl_context = 'mesh_edit' 1087 | bl_label = "Offset Edges" 1088 | bl_options = {'DEFAULT_CLOSED'} 1089 | 1090 | def draw(self, context): 1091 | layout = self.layout 1092 | layout.operator_context = 'EXEC_DEFAULT' 1093 | layout.operator_enum('mesh.offset_edges', 'geometry_mode') 1094 | layout.separator() 1095 | layout.operator_context = 'INVOKE_DEFAULT' 1096 | layout.operator('mesh.offset_edges_profile', text='Profile') 1097 | 1098 | 1099 | def draw_item(self, context): 1100 | self.layout.menu("VIEW3D_MT_edit_mesh_offset_edges") 1101 | 1102 | 1103 | def register(): 1104 | bpy.utils.register_module(__name__) 1105 | bpy.types.VIEW3D_MT_edit_mesh_edges.append(draw_item) 1106 | 1107 | 1108 | def unregister(): 1109 | bpy.utils.unregister_module(__name__) 1110 | bpy.types.VIEW3D_MT_edit_mesh_edges.remove(draw_item) 1111 | 1112 | 1113 | if __name__ == '__main__': 1114 | register() 1115 | -------------------------------------------------------------------------------- /mod_imp.py: -------------------------------------------------------------------------------- 1 | MODULE_NAME = "io_curve_gcode" 2 | 3 | import sys 4 | import os 5 | import bpy 6 | import imp 7 | 8 | mod = sys.modules.get(MODULE_NAME, None) 9 | if mod: 10 | mod.unregister() 11 | imp.reload(mod) 12 | else: 13 | blend_dir = os.path.dirname(bpy.data.filepath) 14 | if blend_dir not in sys.path: 15 | sys.path.append(blend_dir) 16 | mod = __import__(MODULE_NAME) 17 | mod.register() 18 | -------------------------------------------------------------------------------- /modified/HairNet_modified.py: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------- 2 | # File HairNet.py 3 | # Written by Rhett Jackson April 1, 2013 4 | # Some routines were copied from "Curve Loop" by Crouch https://sites.google.com/site/bartiuscrouch/scripts/curveloop 5 | # Some routines were copied from other sources 6 | # Very limited at this time: 7 | # NB 1) After running the script to create hair, the user MUST manually enter Particle Mode on the Head object and "touch" each point of each hair guide. Using a large comb brish with very low strength is a good way to do this. If it's not done, the hair strands are likely to be reset to a default/straight-out position during editing. 8 | # NB 2) All meshes must have the same number of vertices in the direction that corresponds to hair growth 9 | #--------------------------------------------------- 10 | 11 | bl_info = { 12 | "name":"HairNet", 13 | "author": "Rhett Jackson", 14 | "version": (0,4,5), 15 | "blender": (2,6,7), 16 | "location": "Properties", 17 | "category": "Particle", 18 | "description": "Creates a particle hair system with hair guides from mesh edges which start at marked seams.", 19 | "wiki_url": "http://wiki.blender.org/index.php?title=Extensions:2.6/Py/Scripts/Objects/HairNet", 20 | "tracker_url":"http://projects.blender.org/tracker/index.php?func=detail&aid=35062&group_id=153&atid=467" 21 | } 22 | 23 | import bpy 24 | from bpy.utils import register_module, unregister_module 25 | from bpy.props import StringProperty, BoolProperty 26 | 27 | def debPrintVertEdges(vert_edges): 28 | print("vert_edges: ") 29 | for vert in vert_edges: 30 | print(vert, ": ", vert_edges[vert]) 31 | 32 | def debPrintEdgeFaces(edge_faces): 33 | print("edge_faces: ") 34 | for edge in edge_faces: 35 | print(edge, ": ", edge_faces[edge]) 36 | 37 | def debPrintHairGuides(hairGuides): 38 | print("Hair Guides:") 39 | guideN=0 40 | 41 | for group in hairGuides: 42 | print("Guide #",guideN) 43 | i=0 44 | for guide in group: 45 | print(i, " : ", guide) 46 | i += 1 47 | guideN+=1 48 | 49 | def debPrintSeams(seamVerts, seamEdges): 50 | print("Verts in the seam: ") 51 | for vert in seamVerts: 52 | print(vert) 53 | print("Edges in the seam: ") 54 | for edge in seamEdges: 55 | print(edge.index) 56 | 57 | def getEdgeFromKey(mesh,key): 58 | v1 = key[0] 59 | v2 = key[1] 60 | theEdge = 0 61 | for edge in mesh.edges: 62 | if v1 in edge.vertices and v2 in edge.vertices: 63 | #print("Found edge :", edge.index) 64 | return edge 65 | return 0 66 | 67 | def debPrintLoc(func=""): 68 | obj = bpy.context.object 69 | print(obj.name, " ", func) 70 | print("Coords", obj.data.vertices[0].co) 71 | 72 | def createHair(ob, guides, options): 73 | 74 | tempActive = bpy.context.scene.objects.active 75 | bpy.context.scene.objects.active = ob 76 | 77 | nGuides = len(guides) 78 | print("nGguides", nGuides) 79 | nSteps = len(guides[0]) 80 | print("nSteps", nSteps) 81 | 82 | # Create hair particle system if needed 83 | #bpy.ops.object.mode_set(mode='OBJECT') 84 | #bpy.ops.object.particle_system_add() 85 | psys = ob.particle_systems.active 86 | #psys.name = 'HairNet System' 87 | 88 | # Particle settings 89 | pset = psys.settings 90 | 91 | if options[0] != 0: 92 | psys.settings = options[0] 93 | else: 94 | 95 | pset.type = 'HAIR' 96 | pset.name = ''.join([options[1].name, " Hair Settings"]) 97 | 98 | pset.emit_from = 'FACE' 99 | pset.use_render_emitter = True 100 | pset.use_strand_primitive = True 101 | 102 | # Children 103 | pset.child_type = 'SIMPLE' 104 | pset.child_nbr = 6 105 | pset.rendered_child_count = 50 106 | pset.child_length = 1.0 107 | pset.child_length_threshold = 0.0 108 | pset.child_radius = 0.1 109 | pset.child_roundness = 1.0 110 | 111 | pset.hair_step = nSteps-1 112 | pset.count = nGuides 113 | 114 | # Disconnect hair and switch to particle edit mode 115 | bpy.ops.particle.disconnect_hair(all=True) 116 | bpy.ops.particle.particle_edit_toggle() 117 | 118 | # Set all hair-keys 119 | dt = 100.0/(nSteps-1) 120 | dw = 1.0/(nSteps-1) 121 | 122 | # Connect hair to mesh 123 | # Segmentation violation during render if this line is absent. 124 | #Connecting hair moves the mesh points by an amount equal to the object's location 125 | bpy.ops.particle.connect_hair(all=True) 126 | 127 | for m in range(nGuides): 128 | #print("Working on guide #", m) 129 | nSteps = len(guides[m]) 130 | guide = guides[m] 131 | part = psys.particles[m] 132 | bpy.context.scene.tool_settings.particle_edit.use_preserve_root = False 133 | part.location = guide[0] 134 | #bpy.context.scene.tool_settings.particle_edit.use_preserve_root = True 135 | bpy.context.scene.tool_settings.particle_edit.use_preserve_length = False 136 | for n in range(0, nSteps): 137 | point = guide[n] 138 | h = part.hair_keys[n] 139 | #h.co_local = point 140 | h.co = point 141 | h.time = n*dt 142 | h.weight = 1.0 - n*dw 143 | 144 | # Toggle particle edit mode 145 | #bpy.ops.particle.select_all(action='SELECT') 146 | bpy.ops.particle.particle_edit_toggle() 147 | 148 | # Unfortunately, here a manual step appears to be needed: 149 | # 1. Toggle to particle mode 150 | # 2. Touch object with a brush 151 | # 3. Toggle to object mode 152 | # 4. Toggle to edit mode 153 | # 5. Toggle to object mode 154 | # This should correspond to the code below, but fails due to 155 | # wrong context 156 | ''' 157 | bpy.ops.particle.particle_edit_toggle() 158 | bpy.ops.particle.brush_edit() 159 | bpy.ops.particle.particle_edit_toggle() 160 | bpy.ops.object.editmode_toggle() 161 | bpy.ops.object.editmode_toggle() 162 | ''' 163 | bpy.context.scene.objects.active = tempActive 164 | return 165 | 166 | def sortLoop(vloop, v1, seamEdges, vert_edges): 167 | #The hair is either forward or reversed. If it's reversed, reverse it again. Otherwise do nothing. 168 | loop = [] 169 | loopRange = len(vloop)-1 170 | 171 | if vloop[0] == v1.index: 172 | loop = vloop.copy() 173 | 174 | else: 175 | loop = vloop[::-1] 176 | return loop 177 | 178 | def getHairsFromFibers(hair): 179 | me = hair.data 180 | usedV = [] 181 | usedE = [] 182 | guides = [] 183 | 184 | # Create a dictionary with the vert index as key and edge-keys as value 185 | #It's a list of verts and the keys are the edges the verts belong to 186 | vert_edges = dict([(v.index, []) for v in me.vertices if v.hide!=1]) 187 | for ed in me.edges: 188 | for v in ed.key: 189 | if ed.key[0] in vert_edges and ed.key[1] in vert_edges: 190 | vert_edges[v].append(ed.key) 191 | 192 | #endPoints = dict([(v, []) for v in vert_edges if len(vert_edges[v])<2]) 193 | endPoints = [v for v in vert_edges if len(vert_edges[v])<2] 194 | 195 | #For every endpoint 196 | for vert in endPoints: 197 | hair=[] 198 | print("first endpoint is ", vert) 199 | #check if EP has been used already in case it was a tail end already 200 | if vert not in usedV: 201 | #lookup the endpoint in vert_edges to get the edge(s) it's in 202 | thisEdge = getEdgeFromKey(me,vert_edges[vert][0]) 203 | print("Got edge ", thisEdge) 204 | #Add the vert to the hair 205 | hair.append(vert) 206 | #mark the current vert as used 207 | #mark the current edge as used 208 | usedE.append(thisEdge) 209 | usedV.append(vert) 210 | #get the next/other vert in the edge 211 | #make it the current vert 212 | vert = getNextVertInEdge(thisEdge,vert) 213 | print("got next vert ", vert, " edges", vert_edges[vert]) 214 | #while the number of edges the current vert is > 1 215 | while len(vert_edges[vert])>1: 216 | #lookup the endpoint in vert_edges to get the edge(s) it's in 217 | thisEdge = getEdgeFromKey(me,vert_edges[vert][0]) 218 | 219 | if thisEdge in usedE: 220 | thisEdge = getEdgeFromKey(me,vert_edges[vert][1]) 221 | #Add the vert to the hair 222 | hair.append(vert) 223 | #mark the current vert as used 224 | #mark the current edge as used 225 | usedE.append(thisEdge) 226 | usedV.append(vert) 227 | #get the next/other vert in the edge 228 | #make it the current vert 229 | vert = getNextVertInEdge(thisEdge,vert) 230 | print("vert #", vert) 231 | print("edge #", thisEdge) 232 | print(vert_edges[vert]) 233 | 234 | 235 | #Add the current vert to the hair 236 | hair.append(vert) 237 | #mark the current vert as used 238 | usedV.append(vert) 239 | #add the hair to the list of hairs 240 | guides.append(hair) 241 | 242 | #guides now holds a list of hairs where each hair is a list of vertex indices in the mesh "me" 243 | return guides 244 | 245 | # returns all edge loops that a vertex is part of 246 | def getLoops(me, v1, vert_edges, edge_faces, seamEdges): 247 | debug = False 248 | 249 | if not vert_edges: 250 | # Create a dictionary with the vert index as key and edge-keys as value 251 | #It's a list of verts and the keys are the edges the verts belong to 252 | vert_edges = dict([(v.index, []) for v in me.vertices if v.hide!=1]) 253 | for ed in me.edges: 254 | for v in ed.key: 255 | if ed.key[0] in vert_edges and ed.key[1] in vert_edges: 256 | vert_edges[v].append(ed.key) 257 | if debug: debPrintVertEdges(vert_edges) 258 | if not edge_faces: 259 | # Create a dictionary with the edge-key as key and faces as value 260 | # It's a list of edges and the faces they belong to 261 | edge_faces = dict([(ed.key, []) for ed in me.edges if (me.vertices[ed.vertices[0]].hide!=1 and me.vertices[ed.vertices[1]].hide!=1)]) 262 | for f in me.polygons: 263 | for key in f.edge_keys: 264 | if key in edge_faces and f.hide!=1: 265 | edge_faces[key].append(f.index) 266 | if debug : debPrintEdgeFaces(edge_faces) 267 | 268 | ed_used = [] # starting edges that are already part of a loop that is found 269 | edgeloops = [] # to store the final results in 270 | for ed in vert_edges[v1.index]: #ed is all the edges v1 is a part of 271 | if ed in ed_used: 272 | continue 273 | seamTest = getEdgeFromKey(me, ed) 274 | if seamTest.use_seam: 275 | #print("Edge ", seamTest.index, " is a seam") 276 | continue 277 | 278 | vloop = [] # contains all verts of the loop 279 | poles = [] # contains the poles at the ends of the loop 280 | circle = False # tells if loop is circular 281 | n = 0 # to differentiate between the start and the end of the loop 282 | 283 | for m in ed: # for each vert in the edge 284 | n+=1 285 | active_ed = ed 286 | active_v = m 287 | if active_v not in vloop: 288 | vloop.insert(0,active_v) 289 | else: 290 | break 291 | stillGrowing = True 292 | while stillGrowing: 293 | stillGrowing = False 294 | active_f = edge_faces[active_ed] #List of faces the edge belongs to 295 | new_ed = vert_edges[active_v] #list of edges the vert belongs to 296 | if len(new_ed)<3: #only 1 or 2 edges 297 | break 298 | if len(new_ed)>4: #5-face intersection 299 | # detect poles and stop growing 300 | if n>1: 301 | poles.insert(0,vloop.pop(0)) 302 | else: 303 | poles.append(vloop.pop(-1)) 304 | break 305 | for i in new_ed: #new_ed - must have 3 or 4 edges coming from the vert 306 | eliminate = False # if edge shares face, it has to be eliminated 307 | for j in edge_faces[i]: # j is one of the face indices in edge_faces 308 | if j in active_f: 309 | eliminate = True 310 | break 311 | if not eliminate: # it's the next edge in the loop 312 | stillGrowing = True 313 | active_ed = i 314 | if active_ed in vert_edges[v1.index]: #the current edge contains v1 315 | 316 | ed_used.append(active_ed) 317 | for k in active_ed: 318 | if k != active_v: 319 | if k not in vloop: 320 | 321 | if n>1: 322 | vloop.insert(0,k) 323 | else: 324 | vloop.append(k) 325 | 326 | 327 | active_v = k 328 | break 329 | else: 330 | stillGrowing = False # we've come full circle 331 | circle = True 332 | break 333 | #TODO: Function to sort vloop. Use v1 and edge data to walk the ring in order 334 | vloop = sortLoop(vloop, v1, seamEdges, vert_edges) 335 | edgeloops.append([vloop, poles, circle]) 336 | for loop in edgeloops: 337 | for vert in loop[0]: 338 | me.vertices[vert].select=True 339 | #me.edges[edge].select=True 340 | return edgeloops, vert_edges, edge_faces 341 | 342 | 343 | # Hair guides. Four hair with five points each. 344 | def createHairGuides(obj, edgeLoops): 345 | hairGuides = [] 346 | 347 | #For each loop 348 | for loop in edgeLoops: 349 | thisGuide = [] 350 | #For each vert in the loop 351 | for vert in loop[0]: 352 | thisGuide.append(obj.data.vertices[vert].co) 353 | hairGuides.append(thisGuide) 354 | 355 | return hairGuides 356 | 357 | def getSeams(me): 358 | debug = False 359 | #Make a list of all edges marked as seams 360 | error = 0 361 | seamEdges = [] 362 | for edge in me.edges: 363 | if edge.use_seam: 364 | seamEdges.append(edge) 365 | #Make a list of all verts in the seam 366 | seamVerts = [] 367 | for edge in seamEdges: 368 | for vert in edge.vertices: 369 | if vert not in seamVerts: 370 | seamVerts.append(vert) 371 | 372 | if debug: debPrintSeams(seamVerts, seamEdges) 373 | 374 | if(len(seamEdges) == 0): 375 | error = 2 376 | 377 | return seamVerts, seamEdges, error 378 | 379 | def getNextVertInEdge(edge, vert): 380 | if vert == edge.vertices[0]: 381 | return edge.vertices[1] 382 | else: 383 | return edge.vertices[0] 384 | 385 | 386 | def loopsToGuides(me, edgeLoops, hairGuides): 387 | guides = hairGuides 388 | 389 | for loop in edgeLoops: 390 | hair = [] 391 | #hair is a list of coordinate sets. guides is a list of lists 392 | for vert in loop[0]: 393 | 394 | hair.append(me.vertices[vert].co.to_tuple()) 395 | guides.append(hair) 396 | return guides 397 | 398 | def fibersToGuides(hairObj): 399 | guides = [] 400 | hairs = getHairsFromFibers(hairObj) 401 | 402 | for hair in hairs: 403 | guide = [] 404 | for vertIdx in hair: 405 | guide.append(hairObj.data.vertices[vertIdx].co.to_tuple()) 406 | guides.append(guide) 407 | return guides 408 | 409 | 410 | def checkGuides(hairGuides): 411 | length = 0 412 | for guide in hairGuides: 413 | if length == 0: 414 | length = len(guide) 415 | else: 416 | if length != len(guide): 417 | return 1 418 | return 0 419 | 420 | 421 | class HairNet (bpy.types.Operator): 422 | bl_idname = "particle.hairnet" 423 | bl_label = "HairNet" 424 | bl_options = {'REGISTER', 'UNDO'} 425 | bl_description = "Makes hair guides from mesh edges." 426 | 427 | meshKind = StringProperty() 428 | 429 | apply_modifiers = BoolProperty( 430 | name="Apply Modifiers", default=True) 431 | 432 | @classmethod 433 | def poll(self, context): 434 | return(context.mode == 'OBJECT') 435 | 436 | 437 | def execute(self, context): 438 | headObj = context.object 439 | 440 | #Get a list of hair objects 441 | hairObjList = context.selected_objects 442 | hairObjList.remove(headObj) 443 | hairObj = hairObjList[0] 444 | 445 | trans_mat = headObj.matrix_world.inverted() * hairObj.matrix_world 446 | 447 | hairGuides = [] 448 | 449 | error = 0 #0 = All good 450 | #1 = Hair guides have different lengths 451 | #2 = No seams in hair object 452 | 453 | options = [ 454 | 0, #0 the hair system's previous settings 455 | hairObj #1 The hair object 456 | ] 457 | 458 | print("******Start Here*******") 459 | sysName = ''.join(["HN", hairObj.name]) 460 | #Get the head object. It's the last one selected 461 | #headObj = bpy.context.object 462 | 463 | #Preserve hair settings if they exist 464 | if sysName in headObj.particle_systems: 465 | options[0] = headObj.particle_systems[sysName].settings 466 | if sysName != headObj.particle_systems.active.name: 467 | self.report(type = {'ERROR'}, message = "The selected particle system does not match the selected hair object.") 468 | return{'CANCELLED'} 469 | else: 470 | bpy.ops.object.mode_set(mode='OBJECT') 471 | #bpy.ops.object.particle_system_remove() 472 | bpy.ops.object.particle_system_add() 473 | headObj.particle_systems.active.name = sysName 474 | 475 | 476 | psys = headObj.particle_systems[sysName] 477 | psys.name = sysName 478 | 479 | if (self.meshKind=="SHEET"): 480 | print("Hair sheet") 481 | #Create all hair guides 482 | if self.apply_modifiers: 483 | me = hairObj.to_mesh(context.scene, True, 'PREVIEW') 484 | else: 485 | me = hairObj.to_mesh(context.scene, False, 'PREVIEW') 486 | for v in me.vertices: 487 | v.co = trans_mat * v.co 488 | #Identify the seams and their vertices 489 | seamVerts, seamEdges, error = getSeams(me) 490 | 491 | vert_edges = edge_faces = False 492 | #For every vert in a seam, get the edge loop spawned by it 493 | for thisVert in seamVerts: 494 | edgeLoops, vert_edges, edge_faces = getLoops(me, me.vertices[thisVert], vert_edges, edge_faces, seamEdges) 495 | hairGuides = loopsToGuides(me, edgeLoops, hairGuides) 496 | #debPrintHairGuides(hairGuides) 497 | #Take each edge loop and extract coordinate data from its verts 498 | #hairGuides = createHairGuides(hairObj, edgeLoops) 499 | context.blend_data.meshes.remove(me) 500 | 501 | if (self.meshKind=="FIBER"): 502 | print("Hair fiber") 503 | #fibers = getHairsFromFibers(hairObj) 504 | hairGuides = fibersToGuides(hairObj) 505 | 506 | if (self.meshKind=="CURVE"): 507 | #Preserve Active and selected objects 508 | tempActive = headObj = context.object 509 | tempSelected = [] 510 | tempSelected.append(bpy.context.selected_objects[0]) 511 | tempSelected.append(bpy.context.selected_objects[1]) 512 | #hairObj = bpy.context.selected_objects[0] 513 | bpy.ops.object.select_all(action='DESELECT') 514 | 515 | if hairObj.data.bevel_object != None: 516 | error = 3 517 | 518 | 519 | context.scene.objects.active=hairObj 520 | hairObj.select=True 521 | 522 | print("Curve Head: ", headObj.name) 523 | bpy.ops.object.convert(target='MESH', keep_original=True) 524 | fiberObj = context.active_object 525 | 526 | print("Hair Fibers: ", fiberObj.name) 527 | print("Hair Curves: ", hairObj.name) 528 | 529 | hairGuides = fibersToGuides(fiberObj) 530 | 531 | bpy.ops.object.delete(use_global=False) 532 | 533 | #Restore active object and selection 534 | context.scene.objects.active=tempActive 535 | bpy.ops.object.select_all(action='DESELECT') 536 | for sel in tempSelected: 537 | sel.select = True 538 | # return {'FINISHED'} 539 | 540 | if (checkGuides(hairGuides)): 541 | error = 1 542 | 543 | #Process errors 544 | if error != 0: 545 | if error == 1: 546 | self.report(type = {'ERROR'}, message = "Mesh guides have different lengths") 547 | if error == 2: 548 | self.report(type = {'ERROR'}, message = "No seams were defined") 549 | if error == 3: 550 | self.report(type = {'ERROR'}, message = "Cannot create hair from curves with a bevel object") 551 | return{'CANCELLED'} 552 | 553 | debPrintLoc(func="Execute 2") 554 | 555 | #Create the hair guides on the hair object 556 | createHair(headObj, hairGuides, options) 557 | 558 | debPrintLoc(func="Execute 3") 559 | return {'FINISHED'} 560 | 561 | def invoke (self, context, event): 562 | 563 | if len(context.selected_objects) != 2: 564 | self.report(type = {'ERROR'}, message = "Please select two objects") 565 | return {'CANCELLED'} 566 | 567 | return self.execute(context) 568 | 569 | class HairNetPanel(bpy.types.Panel): 570 | bl_idname = "PARTICLE_PT_HairNet" 571 | bl_space_type = "PROPERTIES" 572 | bl_region_type = "WINDOW" 573 | bl_context = "particle" 574 | bl_label = "HairNet 0.4.5" 575 | 576 | def draw(self, context): 577 | obj = context.object 578 | layout = self.layout 579 | 580 | layout.label("HairNet") 581 | 582 | layout.operator("particle.hairnet", text="Add Hair From Sheets").meshKind="SHEET" 583 | layout.operator("particle.hairnet", text="Add Hair From Fibers").meshKind="FIBER" 584 | layout.operator("particle.hairnet", text="Add Hair From Curves").meshKind="CURVE" 585 | 586 | 587 | def register(): 588 | register_module(__name__) 589 | 590 | 591 | 592 | def unregister(): 593 | unregister_module(__name__) 594 | 595 | if __name__ == '__main__': 596 | register() 597 | -------------------------------------------------------------------------------- /modified/add_mesh_ant_landscape_modified.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License 5 | # as published by the Free Software Foundation; either version 2 6 | # of the License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software Foundation, 15 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 | # 17 | # ##### END GPL LICENSE BLOCK ##### 18 | 19 | bl_info = { 20 | "name": "ANT Landscape Modified", 21 | "author": "Jimmy Hazevoet", 22 | "version": (0,1,2), 23 | "blender": (2, 61, 0), 24 | "location": "View3D > Add > Mesh", 25 | "description": "Add a landscape primitive", 26 | "warning": "", # used for warning icon and text in addons panel 27 | "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"\ 28 | "Scripts/Add_Mesh/ANT_Landscape", 29 | "tracker_url": "https://projects.blender.org/tracker/index.php?"\ 30 | "func=detail&aid=23130", 31 | "category": "Add Mesh"} 32 | 33 | """ 34 | Another Noise Tool: Landscape mesh generator 35 | 36 | MESH OPTIONS: 37 | Mesh update: Turn this on for interactive mesh update. 38 | Sphere: Generate sphere or a grid mesh. (Turn height falloff off for sphere mesh) 39 | Rect: Generate rectanble or square mesh. 40 | Smooth: Generate smooth shaded mesh. 41 | Subdivision: Number of mesh subdivisions, higher numbers gives more detail but also slows down the script. 42 | Mesh size: X,Y size of the grid mesh (in blender units). 43 | 44 | NOISE OPTIONS: ( Most of these options are the same as in blender textures. ) 45 | Random seed: Use this to randomise the origin of the noise function. 46 | Noise size: Size of the noise. 47 | Noise type: Available noise types: multiFractal, ridgedMFractal, hybridMFractal, heteroTerrain, Turbulence, Distorted Noise, Cellnoise, Shattered_hTerrain, Marble 48 | Noise basis: Blender, Perlin, NewPerlin, Voronoi_F1, Voronoi_F2, Voronoi_F3, Voronoi_F4, Voronoi_F2-F1, Voronoi Crackle, Cellnoise 49 | VLNoise basis: Blender, Perlin, NewPerlin, Voronoi_F1, Voronoi_F2, Voronoi_F3, Voronoi_F4, Voronoi_F2-F1, Voronoi Crackle, Cellnoise 50 | Distortion: Distortion amount. 51 | Hard: Hard/Soft turbulence noise. 52 | Depth: Noise depth, number of frequencies in the fBm. 53 | Dimension: Musgrave: Fractal dimension of the roughest areas. 54 | Lacunarity: Musgrave: Gap between successive frequencies. 55 | Offset: Musgrave: Raises the terrain from sea level. 56 | Gain: Musgrave: Scale factor. 57 | Marble Bias: Sin, Tri, Saw 58 | Marble Sharpnes: Soft, Sharp, Sharper 59 | Marble Shape: Shape of the marble function: Default, Ring, Swirl, X, Y 60 | 61 | HEIGHT OPTIONS: 62 | Invert: Invert terrain height. 63 | Height: Scale terrain height. 64 | Offset: Terrain height offset. 65 | Falloff: Terrain height falloff: Type 1, Type 2, X, Y, XY 66 | Falloff Size: Terrain falloff size. 67 | This option is activated when Falloff type is X or Y. 68 | Falloff X: Terrain falloff size along X axis. 69 | This option is activated when Falloff type is XY. 70 | Falloff Y: Terrain falloff size along Y axis. 71 | This option is activated when Falloff type is XY. 72 | Sealevel: Flattens terrain below sealevel. 73 | Platlevel: Flattens terrain above plateau level. 74 | Strata: Strata amount, number of strata/terrace layers. 75 | Strata type: Strata types, Smooth, Sharp-sub, Sharp-add 76 | """ 77 | 78 | # import modules 79 | import bpy 80 | from bpy.props import * 81 | from mathutils import * 82 | from mathutils.noise import * 83 | from math import * 84 | 85 | 86 | # Create a new mesh (object) from verts/edges/faces. 87 | # verts/edges/faces ... List of vertices/edges/faces for the 88 | # new mesh (as used in from_pydata). 89 | # name ... Name of the new mesh (& object). 90 | def create_mesh_object(context, verts, edges, faces, name): 91 | # Create new mesh 92 | mesh = bpy.data.meshes.new(name) 93 | 94 | # Make a mesh from a list of verts/edges/faces. 95 | mesh.from_pydata(verts, edges, faces) 96 | 97 | # Update mesh geometry after adding stuff. 98 | mesh.update() 99 | 100 | from bpy_extras import object_utils 101 | return object_utils.object_data_add(context, mesh, operator=None) 102 | 103 | # A very simple "bridge" tool. 104 | # Connects two equally long vertex rows with faces. 105 | # Returns a list of the new faces (list of lists) 106 | # 107 | # vertIdx1 ... First vertex list (list of vertex indices). 108 | # vertIdx2 ... Second vertex list (list of vertex indices). 109 | # closed ... Creates a loop (first & last are closed). 110 | # flipped ... Invert the normal of the face(s). 111 | # 112 | # Note: You can set vertIdx1 to a single vertex index to create 113 | # a fan/star of faces. 114 | # Note: If both vertex idx list are the same length they have 115 | # to have at least 2 vertices. 116 | def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False): 117 | faces = [] 118 | 119 | if not vertIdx1 or not vertIdx2: 120 | return None 121 | 122 | if len(vertIdx1) < 2 and len(vertIdx2) < 2: 123 | return None 124 | 125 | fan = False 126 | if (len(vertIdx1) != len(vertIdx2)): 127 | if (len(vertIdx1) == 1 and len(vertIdx2) > 1): 128 | fan = True 129 | else: 130 | return None 131 | 132 | total = len(vertIdx2) 133 | 134 | if closed: 135 | # Bridge the start with the end. 136 | if flipped: 137 | face = [ 138 | vertIdx1[0], 139 | vertIdx2[0], 140 | vertIdx2[total - 1]] 141 | if not fan: 142 | face.append(vertIdx1[total - 1]) 143 | faces.append(face) 144 | 145 | else: 146 | face = [vertIdx2[0], vertIdx1[0]] 147 | if not fan: 148 | face.append(vertIdx1[total - 1]) 149 | face.append(vertIdx2[total - 1]) 150 | faces.append(face) 151 | 152 | # Bridge the rest of the faces. 153 | for num in range(total - 1): 154 | if flipped: 155 | if fan: 156 | face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]] 157 | else: 158 | face = [vertIdx2[num], vertIdx1[num], 159 | vertIdx1[num + 1], vertIdx2[num + 1]] 160 | faces.append(face) 161 | else: 162 | if fan: 163 | face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]] 164 | else: 165 | face = [vertIdx1[num], vertIdx2[num], 166 | vertIdx2[num + 1], vertIdx1[num + 1]] 167 | faces.append(face) 168 | 169 | return faces 170 | 171 | 172 | ###------------------------------------------------------------ 173 | ###------------------------------------------------------------ 174 | # some functions for marble_noise 175 | def sin_bias(a): 176 | return 0.5 + 0.5 * sin(a) 177 | 178 | def tri_bias(a): 179 | b = 2 * pi 180 | a = 1 - 2 * abs(floor((a * (1/b))+0.5) - (a*(1/b))) 181 | return a 182 | 183 | def saw_bias(a): 184 | b = 2 * pi 185 | n = int(a/b) 186 | a -= n * b 187 | if a < 0: a += b 188 | return a / b 189 | 190 | def soft(a): 191 | return a 192 | 193 | def sharp(a): 194 | return a**0.5 195 | 196 | def sharper(a): 197 | return sharp(sharp(a)) 198 | 199 | def shapes(x,y,shape=0): 200 | if shape == 1: 201 | # ring 202 | x = x*2 203 | y = y*2 204 | s = (-cos(x**2+y**2)/(x**2+y**2+0.5)) 205 | elif shape == 2: 206 | # swirl 207 | x = x*2 208 | y = y*2 209 | s = (( x*sin( x*x+y*y ) + y*cos( x*x+y*y ) ) / (x**2+y**2+0.5)) 210 | elif shape == 3: 211 | # bumps 212 | x = x*2 213 | y = y*2 214 | s = ((cos( x*pi ) + cos( y*pi ))-0.5) 215 | elif shape == 4: 216 | # y grad. 217 | s = (y*pi) 218 | elif shape == 5: 219 | # x grad. 220 | s = (x*pi) 221 | else: 222 | # marble 223 | s = ((x+y)*5) 224 | return s 225 | 226 | # marble_noise 227 | def marble_noise(x,y,z, origin, size, shape, bias, sharpnes, turb, depth, hard, basis ): 228 | x = x / size 229 | y = y / size 230 | z = z / size 231 | s = shapes(x,y,shape) 232 | 233 | x += origin[0] 234 | y += origin[1] 235 | z += origin[2] 236 | value = s + turb * turbulence_vector((x,y,z), depth, hard, basis )[0] 237 | 238 | if bias == 1: 239 | value = tri_bias( value ) 240 | elif bias == 2: 241 | value = saw_bias( value ) 242 | else: 243 | value = sin_bias( value ) 244 | 245 | if sharpnes == 1: 246 | value = sharp( value ) 247 | elif sharpnes == 2: 248 | value = sharper( value ) 249 | else: 250 | value = soft( value ) 251 | 252 | return value 253 | 254 | ###------------------------------------------------------------ 255 | # custom noise types 256 | 257 | # shattered_hterrain: 258 | def shattered_hterrain( x,y,z, H, lacunarity, octaves, offset, distort, basis ): 259 | d = ( turbulence_vector( ( x, y, z ), 6, 0, 0 )[0] * 0.5 + 0.5 )*distort*0.5 260 | t1 = ( turbulence_vector( ( x+d, y+d, z ), 0, 0, 7 )[0] + 0.5 ) 261 | t2 = ( hetero_terrain(( x*2, y*2, z*2 ), H, lacunarity, octaves, offset, basis )*0.5 ) 262 | return (( t1*t2 )+t2*0.5) * 0.5 263 | 264 | # strata_hterrain 265 | def strata_hterrain( x,y,z, H, lacunarity, octaves, offset, distort, basis ): 266 | value = hetero_terrain(( x, y, z ), H, lacunarity, octaves, offset, basis )*0.5 267 | steps = ( sin( value*(distort*5)*pi ) * ( 0.1/(distort*5)*pi ) ) 268 | return ( value * (1.0-0.5) + steps*0.5 ) 269 | 270 | ###------------------------------------------------------------ 271 | # landscape_gen 272 | def landscape_gen(x,y,z,meshsize_x, meshsize_y, 273 | options=[0,1.0,1, 0,0,1.0,0,6,1.0,2.0,1.0,2.0,0,0,0, 1.0,0.0,1,0.0,1.0,0,0,0]): 274 | 275 | # options 276 | rseed = options[0] 277 | nsize = options[1] 278 | ntype = int( options[2] ) 279 | nbasis = int( options[3] ) 280 | vlbasis = int( options[4] ) 281 | distortion = options[5] 282 | hardnoise = options[6] 283 | depth = options[7] 284 | dimension = options[8] 285 | lacunarity = options[9] 286 | offset = options[10] 287 | gain = options[11] 288 | marblebias = int( options[12] ) 289 | marblesharpnes = int( options[13] ) 290 | marbleshape = int( options[14] ) 291 | invert = options[15] 292 | height = options[16] 293 | heightoffset = options[17] 294 | falloff = int( options[18] ) 295 | falloff_size_x = options[19] 296 | falloff_size_y = options[20] 297 | sealevel = options[21] 298 | platlevel = options[22] 299 | strata = options[23] 300 | stratatype = options[24] 301 | sphere = options[25] 302 | 303 | # origin 304 | if rseed == 0: 305 | origin = 0.0,0.0,0.0 306 | origin_x = 0.0 307 | origin_y = 0.0 308 | origin_z = 0.0 309 | else: 310 | # randomise origin 311 | seed_set( rseed ) 312 | origin = random_unit_vector() 313 | origin_x = ( 0.5 - origin[0] ) * 1000.0 314 | origin_y = ( 0.5 - origin[1] ) * 1000.0 315 | origin_z = ( 0.5 - origin[2] ) * 1000.0 316 | 317 | # adjust noise size and origin 318 | ncoords = ( x / nsize + origin_x, y / nsize + origin_y, z / nsize + origin_z ) 319 | 320 | # noise basis type's 321 | if nbasis == 9: nbasis = 14 # to get cellnoise basis you must set 14 instead of 9 322 | if vlbasis ==9: vlbasis = 14 323 | # noise type's 324 | if ntype == 0: value = multi_fractal( ncoords, dimension, lacunarity, depth, nbasis ) * 0.5 325 | elif ntype == 1: value = ridged_multi_fractal( ncoords, dimension, lacunarity, depth, offset, gain, nbasis ) * 0.5 326 | elif ntype == 2: value = hybrid_multi_fractal( ncoords, dimension, lacunarity, depth, offset, gain, nbasis ) * 0.5 327 | elif ntype == 3: value = hetero_terrain( ncoords, dimension, lacunarity, depth, offset, nbasis ) * 0.25 328 | elif ntype == 4: value = fractal( ncoords, dimension, lacunarity, depth, nbasis ) 329 | elif ntype == 5: value = turbulence_vector( ncoords, depth, hardnoise, nbasis )[0] 330 | elif ntype == 6: value = variable_lacunarity( ncoords, distortion, nbasis, vlbasis ) + 0.5 331 | elif ntype == 7: value = marble_noise( x*2.0/meshsize_x,y*2.0/meshsize_y,z*2.0*2/(meshsize_x+meshsize_y), origin, nsize, marbleshape, marblebias, marblesharpnes, distortion, depth, hardnoise, nbasis ) 332 | elif ntype == 8: value = shattered_hterrain( ncoords[0], ncoords[1], ncoords[2], dimension, lacunarity, depth, offset, distortion, nbasis ) 333 | elif ntype == 9: value = strata_hterrain( ncoords[0], ncoords[1], ncoords[2], dimension, lacunarity, depth, offset, distortion, nbasis ) 334 | else: 335 | value = 0.0 336 | 337 | # adjust height 338 | if invert !=0: 339 | value = (1-value) * height + heightoffset 340 | else: 341 | value = value * height + heightoffset 342 | 343 | # edge falloff 344 | if sphere == 0: # no edge falloff if spherical 345 | if falloff: 346 | ratio_x, ratio_y = abs(x) * 2 / meshsize_x, abs(y) * 2 / meshsize_y 347 | fallofftypes = [0, 348 | sqrt(ratio_x**4 + ratio_y**4), 349 | sqrt(ratio_x**2 + ratio_y**2), 350 | sqrt(ratio_y**falloff_size_y), 351 | sqrt(ratio_x**falloff_size_x), 352 | sqrt(ratio_x**falloff_size_x 353 | + ratio_y**falloff_size_y), 354 | ] 355 | dist = fallofftypes[falloff] 356 | value = value - sealevel 357 | if dist < 1.0: 358 | dist = ( (dist) * (dist) * ( 3-2*(dist) ) ) 359 | value = ( value - value * dist ) + sealevel 360 | else: 361 | value = sealevel 362 | 363 | # strata / terrace / layered 364 | if stratatype !='0': 365 | strata = strata / height 366 | if stratatype == '1': 367 | strata *= 2 368 | steps = ( sin( value*strata*pi ) * ( 0.1/strata*pi ) ) 369 | value = ( value * (1.0-0.5) + steps*0.5 ) * 2.0 370 | elif stratatype == '2': 371 | steps = -abs( sin( value*(strata)*pi ) * ( 0.1/(strata)*pi ) ) 372 | value =( value * (1.0-0.5) + steps*0.5 ) * 2.0 373 | elif stratatype == '3': 374 | steps = abs( sin( value*(strata)*pi ) * ( 0.1/(strata)*pi ) ) 375 | value =( value * (1.0-0.5) + steps*0.5 ) * 2.0 376 | else: 377 | value = value 378 | 379 | # clamp height 380 | if ( value < sealevel ): value = sealevel 381 | if ( value > platlevel ): value = platlevel 382 | 383 | return value 384 | 385 | 386 | # generate grid 387 | def grid_gen( sub_d, size_me_x, size_me_y, options ): 388 | 389 | verts = [] 390 | faces = [] 391 | edgeloop_prev = [] 392 | 393 | size_me_larger = size_me_x if size_me_x >= size_me_y else size_me_y 394 | delta = size_me_larger / float(sub_d - 1) 395 | sub_d_x = round(size_me_x / delta) 396 | sub_d_y = round(size_me_y / delta) 397 | 398 | start_x = -(size_me_x / 2.0) 399 | start_y = -(size_me_y / 2.0) 400 | 401 | for row_x in range(sub_d_x): 402 | edgeloop_cur = [] 403 | x = start_x + row_x * delta 404 | for row_y in range(sub_d_y): 405 | y = start_y + row_y * delta 406 | z = landscape_gen(x, y, 0.0, size_me_x, size_me_y, options) 407 | 408 | edgeloop_cur.append(len(verts)) 409 | verts.append((x,y,z)) 410 | 411 | if len(edgeloop_prev) > 0: 412 | faces_row = createFaces(edgeloop_prev, edgeloop_cur) 413 | faces.extend(faces_row) 414 | 415 | edgeloop_prev = edgeloop_cur 416 | 417 | return verts, faces 418 | 419 | 420 | # generate sphere 421 | def sphere_gen( sub_d, size_me, options ): 422 | 423 | verts = [] 424 | faces = [] 425 | edgeloop_prev = [] 426 | 427 | for row_x in range(sub_d): 428 | edgeloop_cur = [] 429 | for row_y in range(sub_d): 430 | u = sin(row_y*pi*2/(sub_d-1)) * cos(-pi/2+row_x*pi/(sub_d-1)) * size_me/2 431 | v = cos(row_y*pi*2/(sub_d-1)) * cos(-pi/2+row_x*pi/(sub_d-1)) * size_me/2 432 | w = sin(-pi/2+row_x*pi/(sub_d-1)) * size_me/2 433 | h = landscape_gen(u,v,w,size_me,size_me, options) / size_me 434 | u,v,w = u+u*h, v+v*h, w+w*h 435 | 436 | edgeloop_cur.append(len(verts)) 437 | verts.append((u, v, w)) 438 | 439 | if len(edgeloop_prev) > 0: 440 | faces_row = createFaces(edgeloop_prev, edgeloop_cur) 441 | faces.extend(faces_row) 442 | 443 | edgeloop_prev = edgeloop_cur 444 | 445 | return verts, faces 446 | 447 | 448 | ###------------------------------------------------------------ 449 | # Add landscape 450 | class landscape_add(bpy.types.Operator): 451 | """Add a landscape mesh""" 452 | bl_idname = "mesh.landscape_modified_add" 453 | bl_label = "Landscape Modified" 454 | bl_options = {'REGISTER', 'UNDO', 'PRESET'} 455 | bl_description = "Add landscape mesh" 456 | 457 | # properties 458 | AutoUpdate = BoolProperty(name="Mesh update", 459 | default=True, 460 | description="Update mesh") 461 | 462 | SphereMesh = BoolProperty(name="Sphere", 463 | default=False, 464 | description="Generate Sphere mesh") 465 | 466 | RectMesh = BoolProperty(name="Rect", 467 | default=False, 468 | description="Not square mesh") 469 | 470 | SmoothMesh = BoolProperty(name="Smooth", 471 | default=True, 472 | description="Shade smooth") 473 | 474 | Subdivision = IntProperty(name="Subdivisions", 475 | min=4, 476 | max=6400, 477 | default=64, 478 | description="Mesh x y subdivisions") 479 | 480 | MeshSize = FloatProperty(name="Mesh Size", 481 | min=0.01, 482 | max=100.0, 483 | default=2.0, 484 | description="Mesh size") 485 | 486 | MeshSizeX = FloatProperty(name="Mesh Size X", 487 | min=0.01, 488 | max=100.0, 489 | default=2.0, 490 | description="Mesh size X") 491 | 492 | MeshSizeY = FloatProperty(name="Mesh Size Y", 493 | min=0.01, 494 | max=100.0, 495 | default=2.0, 496 | description="Mesh size Y") 497 | 498 | RandomSeed = IntProperty(name="Random Seed", 499 | min=0, 500 | max=9999, 501 | default=0, 502 | description="Randomize noise origin") 503 | 504 | NoiseSize = FloatProperty(name="Noise Size", 505 | min=0.01, 506 | max=10000.0, 507 | default=1.0, 508 | description="Noise size") 509 | 510 | NoiseTypes = [ 511 | ("0","multiFractal","multiFractal"), 512 | ("1","ridgedMFractal","ridgedMFractal"), 513 | ("2","hybridMFractal","hybridMFractal"), 514 | ("3","heteroTerrain","heteroTerrain"), 515 | ("4","fBm","fBm"), 516 | ("5","Turbulence","Turbulence"), 517 | ("6","Distorted Noise","Distorted Noise"), 518 | ("7","Marble","Marble"), 519 | ("8","Shattered_hTerrain","Shattered_hTerrain"), 520 | ("9","Strata_hTerrain","Strata_hTerrain")] 521 | 522 | NoiseType = EnumProperty(name="Type", 523 | description="Noise type", 524 | items=NoiseTypes) 525 | 526 | BasisTypes = [ 527 | ("0","Blender","Blender"), 528 | ("1","Perlin","Perlin"), 529 | ("2","NewPerlin","NewPerlin"), 530 | ("3","Voronoi_F1","Voronoi_F1"), 531 | ("4","Voronoi_F2","Voronoi_F2"), 532 | ("5","Voronoi_F3","Voronoi_F3"), 533 | ("6","Voronoi_F4","Voronoi_F4"), 534 | ("7","Voronoi_F2-F1","Voronoi_F2-F1"), 535 | ("8","Voronoi Crackle","Voronoi Crackle"), 536 | ("9","Cellnoise","Cellnoise")] 537 | BasisType = EnumProperty(name="Basis", 538 | description="Noise basis", 539 | items=BasisTypes) 540 | 541 | VLBasisTypes = [ 542 | ("0","Blender","Blender"), 543 | ("1","Perlin","Perlin"), 544 | ("2","NewPerlin","NewPerlin"), 545 | ("3","Voronoi_F1","Voronoi_F1"), 546 | ("4","Voronoi_F2","Voronoi_F2"), 547 | ("5","Voronoi_F3","Voronoi_F3"), 548 | ("6","Voronoi_F4","Voronoi_F4"), 549 | ("7","Voronoi_F2-F1","Voronoi_F2-F1"), 550 | ("8","Voronoi Crackle","Voronoi Crackle"), 551 | ("9","Cellnoise","Cellnoise")] 552 | VLBasisType = EnumProperty(name="VLBasis", 553 | description="VLNoise basis", 554 | items=VLBasisTypes) 555 | 556 | Distortion = FloatProperty(name="Distortion", 557 | min=0.01, 558 | max=1000.0, 559 | default=1.0, 560 | description="Distortion amount") 561 | 562 | HardNoise = BoolProperty(name="Hard", 563 | default=True, 564 | description="Hard noise") 565 | 566 | NoiseDepth = IntProperty(name="Depth", 567 | min=1, 568 | max=16, 569 | default=6, 570 | description="Noise Depth - number of frequencies in the fBm") 571 | 572 | mDimension = FloatProperty(name="Dimension", 573 | min=0.01, 574 | max=2.0, 575 | default=1.0, 576 | description="H - fractal dimension of the roughest areas") 577 | 578 | mLacunarity = FloatProperty(name="Lacunarity", 579 | min=0.01, 580 | max=6.0, 581 | default=2.0, 582 | description="Lacunarity - gap between successive frequencies") 583 | 584 | mOffset = FloatProperty(name="Offset", 585 | min=0.01, 586 | max=6.0, 587 | default=1.0, 588 | description="Offset - raises the terrain from sea level") 589 | 590 | mGain = FloatProperty(name="Gain", 591 | min=0.01, 592 | max=6.0, 593 | default=1.0, 594 | description="Gain - scale factor") 595 | 596 | BiasTypes = [ 597 | ("0","Sin","Sin"), 598 | ("1","Tri","Tri"), 599 | ("2","Saw","Saw")] 600 | MarbleBias = EnumProperty(name="Bias", 601 | description="Marble bias", 602 | items=BiasTypes) 603 | 604 | SharpTypes = [ 605 | ("0","Soft","Soft"), 606 | ("1","Sharp","Sharp"), 607 | ("2","Sharper","Sharper")] 608 | MarbleSharp = EnumProperty(name="Sharp", 609 | description="Marble sharp", 610 | items=SharpTypes) 611 | 612 | ShapeTypes = [ 613 | ("0","Default","Default"), 614 | ("1","Ring","Ring"), 615 | ("2","Swirl","Swirl"), 616 | ("3","Bump","Bump"), 617 | ("4","Y","Y"), 618 | ("5","X","X")] 619 | MarbleShape = EnumProperty(name="Shape", 620 | description="Marble shape", 621 | items=ShapeTypes) 622 | 623 | Invert = BoolProperty(name="Invert", 624 | default=False, 625 | description="Invert noise input") 626 | 627 | Height = FloatProperty(name="Height", 628 | min=0.01, 629 | max=10000.0, 630 | default=0.5, 631 | description="Height scale") 632 | 633 | Offset = FloatProperty(name="Offset", 634 | min=-10000.0, 635 | max=10000.0, 636 | default=0.0, 637 | description="Height offset") 638 | 639 | fallTypes = [ 640 | ("0","None","None"), 641 | ("1","Type 1","Type 1"), 642 | ("2","Type 2","Type 2"), 643 | ("3","Y","Y"), 644 | ("4","X","X"), 645 | ("5","XY","XY"), 646 | ] 647 | Falloff = EnumProperty(name="Falloff", 648 | description="Edge falloff", 649 | default="1", 650 | items=fallTypes) 651 | 652 | FalloffSize = FloatProperty(name="Falloff Size", 653 | min=1.0, 654 | max=10.0, 655 | default=2.0, 656 | description="Falloff Size") 657 | 658 | FalloffSizeX = FloatProperty(name="Falloff X", 659 | min=1.0, 660 | max=10.0, 661 | default=4.0, 662 | description="Falloff Size X") 663 | 664 | FalloffSizeY = FloatProperty(name="Falloff Y", 665 | min=1.0, 666 | max=10.0, 667 | default=4.0, 668 | description="Falloff Size Y") 669 | 670 | Sealevel = FloatProperty(name="Sealevel", 671 | min=-10000.0, 672 | max=10000.0, 673 | default=0.0, 674 | description="Sealevel") 675 | 676 | Plateaulevel = FloatProperty(name="Plateau", 677 | min=-10000.0, 678 | max=10000.0, 679 | default=1.0, 680 | description="Plateau level") 681 | 682 | Strata = FloatProperty(name="Strata", 683 | min=0.01, 684 | max=1000.0, 685 | default=3.0, 686 | description="Strata amount") 687 | 688 | StrataTypes = [ 689 | ("0","None","None"), 690 | ("1","Type 1","Type 1"), 691 | ("2","Type 2","Type 2"), 692 | ("3","Type 3","Type 3")] 693 | StrataType = EnumProperty(name="Strata", 694 | description="Strata type", 695 | default="0", 696 | items=StrataTypes) 697 | 698 | ###------------------------------------------------------------ 699 | # Draw 700 | def draw(self, context): 701 | layout = self.layout 702 | 703 | box = layout.box() 704 | box.prop(self, 'AutoUpdate') 705 | box.prop(self, 'SphereMesh') 706 | if not self.SphereMesh: 707 | box.prop(self, 'RectMesh') 708 | box.prop(self, 'SmoothMesh') 709 | box.prop(self, 'Subdivision') 710 | if self.SphereMesh or not self.RectMesh: 711 | box.prop(self, 'MeshSize') 712 | else: 713 | box.prop(self, 'MeshSizeX') 714 | box.prop(self, 'MeshSizeY') 715 | 716 | box = layout.box() 717 | box.prop(self, 'NoiseType') 718 | if self.NoiseType != '7': 719 | box.prop(self, 'BasisType') 720 | box.prop(self, 'RandomSeed') 721 | box.prop(self, 'NoiseSize') 722 | if self.NoiseType == '0': 723 | box.prop(self, 'NoiseDepth') 724 | box.prop(self, 'mDimension') 725 | box.prop(self, 'mLacunarity') 726 | elif self.NoiseType == '1': 727 | box.prop(self, 'NoiseDepth') 728 | box.prop(self, 'mDimension') 729 | box.prop(self, 'mLacunarity') 730 | box.prop(self, 'mOffset') 731 | box.prop(self, 'mGain') 732 | elif self.NoiseType == '2': 733 | box.prop(self, 'NoiseDepth') 734 | box.prop(self, 'mDimension') 735 | box.prop(self, 'mLacunarity') 736 | box.prop(self, 'mOffset') 737 | box.prop(self, 'mGain') 738 | elif self.NoiseType == '3': 739 | box.prop(self, 'NoiseDepth') 740 | box.prop(self, 'mDimension') 741 | box.prop(self, 'mLacunarity') 742 | box.prop(self, 'mOffset') 743 | elif self.NoiseType == '4': 744 | box.prop(self, 'NoiseDepth') 745 | box.prop(self, 'mDimension') 746 | box.prop(self, 'mLacunarity') 747 | elif self.NoiseType == '5': 748 | box.prop(self, 'NoiseDepth') 749 | box.prop(self, 'HardNoise') 750 | elif self.NoiseType == '6': 751 | box.prop(self, 'VLBasisType') 752 | box.prop(self, 'Distortion') 753 | elif self.NoiseType == '7': 754 | box.prop(self, 'MarbleShape') 755 | box.prop(self, 'MarbleBias') 756 | box.prop(self, 'MarbleSharp') 757 | box.prop(self, 'Distortion') 758 | box.prop(self, 'NoiseDepth') 759 | box.prop(self, 'HardNoise') 760 | elif self.NoiseType == '8': 761 | box.prop(self, 'NoiseDepth') 762 | box.prop(self, 'mDimension') 763 | box.prop(self, 'mLacunarity') 764 | box.prop(self, 'mOffset') 765 | box.prop(self, 'Distortion') 766 | elif self.NoiseType == '9': 767 | box.prop(self, 'NoiseDepth') 768 | box.prop(self, 'mDimension') 769 | box.prop(self, 'mLacunarity') 770 | box.prop(self, 'mOffset') 771 | box.prop(self, 'Distortion') 772 | 773 | box = layout.box() 774 | box.prop(self, 'Invert') 775 | box.prop(self, 'Height') 776 | box.prop(self, 'Offset') 777 | box.prop(self, 'Plateaulevel') 778 | box.prop(self, 'Sealevel') 779 | if not self.SphereMesh: 780 | box.prop(self, 'Falloff') 781 | if self.Falloff == '3' or self.Falloff == '4': 782 | box.prop(self, 'FalloffSize') 783 | elif self.Falloff == '5': 784 | box.prop(self, 'FalloffSizeX') 785 | box.prop(self, 'FalloffSizeY') 786 | box.prop(self, 'StrataType') 787 | if self.StrataType != '0': 788 | box.prop(self, 'Strata') 789 | 790 | ###------------------------------------------------------------ 791 | # Execute 792 | def execute(self, context): 793 | 794 | #mesh update 795 | if self.AutoUpdate is True: 796 | 797 | # turn off undo 798 | undo = bpy.context.user_preferences.edit.use_global_undo 799 | bpy.context.user_preferences.edit.use_global_undo = False 800 | 801 | # deselect all objects when in object mode 802 | if bpy.ops.object.select_all.poll(): 803 | bpy.ops.object.select_all(action='DESELECT') 804 | 805 | # options 806 | options = [ 807 | self.RandomSeed, #0 808 | self.NoiseSize, #1 809 | self.NoiseType, #2 810 | self.BasisType, #3 811 | self.VLBasisType, #4 812 | self.Distortion, #5 813 | self.HardNoise, #6 814 | self.NoiseDepth, #7 815 | self.mDimension, #8 816 | self.mLacunarity, #9 817 | self.mOffset, #10 818 | self.mGain, #11 819 | self.MarbleBias, #12 820 | self.MarbleSharp, #13 821 | self.MarbleShape, #14 822 | self.Invert, #15 823 | self.Height, #16 824 | self.Offset, #17 825 | self.Falloff, #18 826 | self.FalloffSizeX, #19 827 | self.FalloffSizeY, #20 828 | self.Sealevel, #21 829 | self.Plateaulevel, #22 830 | self.Strata, #23 831 | self.StrataType, #24 832 | self.SphereMesh #25 833 | ] 834 | if self.Falloff != '5': 835 | options[19] = options[20] = self.FalloffSize 836 | 837 | # Main function 838 | if self.SphereMesh: 839 | # sphere 840 | verts, faces = sphere_gen(self.Subdivision, self.MeshSize, options) 841 | elif not self.RectMesh: 842 | # square grid 843 | verts, faces = grid_gen( 844 | self.Subdivision, self.MeshSize, self.MeshSize, options) 845 | else: 846 | # rectangle grid 847 | verts, faces = grid_gen( 848 | self.Subdivision, self.MeshSizeX, self.MeshSizeY, options) 849 | 850 | # create mesh object 851 | obj = create_mesh_object(context, verts, [], faces, "Landscape") 852 | bpy.ops.object.mode_set(mode='EDIT') 853 | bpy.ops.mesh.normals_make_consistent(inside=False) 854 | bpy.ops.object.mode_set(mode='OBJECT') 855 | # sphere, remove doubles 856 | if self.SphereMesh !=0: 857 | bpy.ops.object.mode_set(mode='EDIT') 858 | bpy.ops.mesh.remove_doubles(threshold=0.0001) 859 | bpy.ops.object.mode_set(mode='OBJECT') 860 | 861 | # Shade smooth 862 | if self.SmoothMesh !=0: 863 | if bpy.ops.object.shade_smooth.poll(): 864 | bpy.ops.object.shade_smooth() 865 | else: # edit mode 866 | bpy.ops.mesh.faces_shade_smooth() 867 | 868 | # restore pre operator undo state 869 | bpy.context.user_preferences.edit.use_global_undo = undo 870 | 871 | return {'FINISHED'} 872 | else: 873 | return {'PASS_THROUGH'} 874 | 875 | 876 | ###------------------------------------------------------------ 877 | # Register 878 | 879 | # Define "Landscape" menu 880 | def menu_func_landscape(self, context): 881 | self.layout.operator(landscape_add.bl_idname, text="Landscape Modified", icon="PLUGIN") 882 | 883 | def register(): 884 | bpy.utils.register_module(__name__) 885 | 886 | bpy.types.INFO_MT_mesh_add.append(menu_func_landscape) 887 | 888 | def unregister(): 889 | bpy.utils.unregister_module(__name__) 890 | 891 | bpy.types.INFO_MT_mesh_add.remove(menu_func_landscape) 892 | 893 | if __name__ == "__main__": 894 | register() 895 | -------------------------------------------------------------------------------- /modified/space_view3d_viewport_roll_modified.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from mathutils import * 3 | from math import * 4 | 5 | 6 | bl_info = { 7 | "name": "Viewport Roll", 8 | "author": "Jace Priester", 9 | "version": (1, 0, 1), 10 | "blender": (2, 6, 3), 11 | "api": 45996, 12 | "location": "View 3D -> Ctrl-Shift-Mousewheel and Alt-Middle-Drag", 13 | "description": "Adds roll capability to the 3d viewport", 14 | "warning": "", 15 | "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\ 16 | "Scripts/My_Script", 17 | "tracker_url": "http://projects.blender.org/tracker/index.php?"\ 18 | "func=detail&aid=", 19 | "category": "3D View"} 20 | 21 | CAMNORMAL = Vector((0, 0, -1)) 22 | 23 | class RollViewportCW(bpy.types.Operator): 24 | '''Roll the viewport clockwise.''' 25 | bl_idname = "view3d.roll_viewport_cw" 26 | bl_label = "Roll the viewport clockwise" 27 | bl_options = {'GRAB_POINTER'} 28 | 29 | def execute(self, context): 30 | v3d = context.space_data 31 | rv3d = v3d.region_3d 32 | if rv3d.view_perspective == 'CAMERA': 33 | rv3d.view_perspective = 'PERSP' 34 | #camNormal=Vector((0,0,-1)) 35 | angle_diff=bpy.context.user_preferences.view.rotation_angle*pi/180.0 36 | q=Quaternion(CAMNORMAL,-angle_diff) 37 | rv3d.view_rotation*=q 38 | 39 | return {'FINISHED'} 40 | 41 | class RollViewportCCW(bpy.types.Operator): 42 | '''Roll the viewport counterclockwise.''' 43 | bl_idname = "view3d.roll_viewport_ccw" 44 | bl_label = "Roll the viewport counterclockwise" 45 | bl_options = {'GRAB_POINTER'} 46 | 47 | def execute(self, context): 48 | v3d = context.space_data 49 | rv3d = v3d.region_3d 50 | if rv3d.view_perspective == 'CAMERA': 51 | rv3d.view_perspective = 'PERSP' 52 | #camNormal=Vector((0,0,-1)) 53 | angle_diff=bpy.context.user_preferences.view.rotation_angle*pi/180.0 54 | q=Quaternion(CAMNORMAL,angle_diff) 55 | rv3d.view_rotation*=q 56 | 57 | return {'FINISHED'} 58 | 59 | class RollViewport(bpy.types.Operator): 60 | '''Roll the viewport.''' 61 | bl_idname = "view3d.roll_viewport" 62 | bl_label = "Roll the viewport" 63 | bl_options = {'GRAB_POINTER'} 64 | 65 | initial_angle=0 66 | angle_now=0 67 | initial_rotation=Vector((0,0,0)) 68 | 69 | def execute(self, context): 70 | v3d = context.space_data 71 | rv3d = v3d.region_3d 72 | 73 | #camNormal=Vector((0,0,-1)) 74 | angle_diff=self.angle_now-self.initial_angle 75 | q=Quaternion(CAMNORMAL,angle_diff) 76 | rv3d.view_rotation=self.initial_rotation*q 77 | 78 | return {'FINISHED'} 79 | 80 | def modal(self, context, event): 81 | v3d = context.space_data 82 | rv3d = v3d.region_3d 83 | 84 | if event.type == 'MOUSEMOVE': 85 | mouseloc=Vector((event.mouse_region_x,event.mouse_region_y)) 86 | mouseloc_centered=mouseloc-self.center 87 | self.angle_now=atan2(mouseloc_centered.y,mouseloc_centered.x) 88 | self.execute(context) 89 | #context.area.header_text_set("Offset %.4f %.4f %.4f" % tuple(self.offset)) 90 | 91 | elif event.type in {'LEFTMOUSE', 'MIDDLEMOUSE'}: 92 | context.area.header_text_set() 93 | return {'FINISHED'} 94 | 95 | elif event.type in {'RIGHTMOUSE', 'ESC'}: 96 | rv3d.view_rotation = self.initial_rotation 97 | context.area.header_text_set() 98 | return {'CANCELLED'} 99 | 100 | return {'RUNNING_MODAL'} 101 | 102 | def invoke(self, context, event): 103 | 104 | if context.space_data.type == 'VIEW_3D': 105 | v3d = context.space_data 106 | rv3d = v3d.region_3d 107 | 108 | context.window_manager.modal_handler_add(self) 109 | 110 | if rv3d.view_perspective == 'CAMERA': 111 | rv3d.view_perspective = 'PERSP' 112 | 113 | 114 | self.windowsize=Vector((context.region.width,context.region.height)) 115 | self.center=self.windowsize/2 116 | mouseloc=Vector((event.mouse_region_x,event.mouse_region_y)) 117 | mouseloc_centered=mouseloc-self.center 118 | self.initial_rotation=rv3d.view_rotation.copy() 119 | self.initial_angle=atan2(mouseloc_centered.y,mouseloc_centered.x) 120 | self.angle_now=self.initial_angle 121 | 122 | return {'RUNNING_MODAL'} 123 | else: 124 | self.report({'WARNING'}, "Active space must be a View3d") 125 | return {'CANCELLED'} 126 | 127 | 128 | def register(): 129 | bpy.utils.register_class(RollViewport) 130 | bpy.utils.register_class(RollViewportCW) 131 | bpy.utils.register_class(RollViewportCCW) 132 | 133 | wm = bpy.context.window_manager 134 | km = wm.keyconfigs.addon.keymaps.new(name="3D View", space_type='VIEW_3D') 135 | kmi = km.keymap_items.new('view3d.roll_viewport', 'MIDDLEMOUSE', 'PRESS', shift=False, ctrl=False, alt=True) 136 | kmi = km.keymap_items.new('view3d.roll_viewport_ccw', 'WHEELUPMOUSE', 'PRESS', shift=True, ctrl=True, alt=False) 137 | kmi = km.keymap_items.new('view3d.roll_viewport_cw', 'WHEELDOWNMOUSE', 'PRESS', shift=True, ctrl=True, alt=False) 138 | kmi = km.keymap_items.new('view3d.roll_viewport_ccw', 'NUMPAD_4', 'PRESS', shift=False, ctrl=False, alt=True) 139 | kmi = km.keymap_items.new('view3d.roll_viewport_cw', 'NUMPAD_6', 'PRESS', shift=False, ctrl=False, alt=True) 140 | 141 | 142 | def unregister(): 143 | bpy.utils.unregister_class(RollViewport) 144 | bpy.utils.unregister_class(RollViewportCW) 145 | bpy.utils.unregister_class(RollViewportCCW) 146 | 147 | wm = bpy.context.window_manager 148 | km = wm.keyconfigs.addon.keymaps['3D View'] 149 | for kmi in km.keymap_items: 150 | if kmi.idname == 'view3d.roll_viewport': 151 | km.keymap_items.remove(kmi) 152 | elif kmi.idname == 'view3d.roll_viewport_ccw': 153 | km.keymap_items.remove(kmi) 154 | elif kmi.idname == 'view3d.roll_viewport_cw': 155 | km.keymap_items.remove(kmi) 156 | 157 | 158 | if __name__ == "__main__": 159 | register() 160 | -------------------------------------------------------------------------------- /object_apply_transform_multiuser.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN GPL LICENSE BLOCK ***** 2 | # 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ***** END GPL LICENCE BLOCK ***** 19 | 20 | bl_info = { 21 | "name": "Apply Transform MultiUser", 22 | "author": "Hidesato Ikeya", 23 | "version": (1, 2), 24 | "blender": (2, 68, 0), 25 | "location": "View3D > CTRL-A > Multiuser", 26 | "description": "Apply transform to multi user objects", 27 | "warning": "", 28 | "wiki_url": "", 29 | "tracker_url": "", 30 | "category": "Object"} 31 | 32 | import bpy 33 | from mathutils import * 34 | 35 | class ApplyTransformMultiUser(bpy.types.Operator): 36 | bl_idname = "object.apply_transform_multiuser" 37 | bl_label = "Apply Transform MultiUser" 38 | bl_options = {'REGISTER', 'UNDO'} 39 | 40 | only_selected = bpy.props.BoolProperty( 41 | name="Only selected", default=False, 42 | description="Only selected objects sharing same data is affected") 43 | 44 | keep_visual = bpy.props.BoolProperty( 45 | name="Keep visual", default=False, 46 | description="Tries to keep visual proportion as much as possible") 47 | 48 | remove_original = bpy.props.BoolProperty( 49 | name="Remove Original Data", default=True, 50 | description="Remove original data if it is used by no user.") 51 | 52 | location = bpy.props.BoolProperty(name="Location", default=False) 53 | rotation = bpy.props.BoolProperty(name="Rotation", default=False) 54 | scale = bpy.props.BoolProperty(name="Scale", default=False) 55 | 56 | def draw(self, context): 57 | layout = self.layout 58 | 59 | layout.prop(self, 'only_selected') 60 | layout.prop(self, 'keep_visual') 61 | layout.prop(self, 'remove_original') 62 | 63 | layout.separator() 64 | 65 | layout.prop(self, 'location') 66 | layout.prop(self, 'rotation') 67 | layout.prop(self, 'scale') 68 | 69 | @classmethod 70 | def poll(self, context): 71 | obtype = context.active_object.type 72 | return context.mode == 'OBJECT' and obtype != 'EMPTY' 73 | 74 | def execute(self, context): 75 | if not context.active_object: 76 | self.report({'ERROR'}, "No active object") 77 | return {'CANCELLED'} 78 | active = context.active_object 79 | selected_objects = set(context.selected_objects) 80 | 81 | if self.keep_visual: 82 | scale_factor = Vector(1.0 / s for s in active.scale) 83 | m = active.matrix_basis.copy() 84 | m.invert() 85 | location_factor = m.to_translation() 86 | rot_factor = m.to_quaternion() 87 | 88 | bpy.ops.object.select_all(action='DESELECT') 89 | context.scene.objects.active = active 90 | bpy.ops.object.select_linked(extend=False, type='OBDATA') 91 | linked_objects = set(ob for ob in context.selected_objects) 92 | linked_objects.remove(active) 93 | if self.only_selected: 94 | linked_objects &= selected_objects 95 | bpy.ops.object.select_all(action='DESELECT') 96 | 97 | orig_data = active.data 98 | new_data = orig_data.copy() 99 | active.data = new_data 100 | active.select = True 101 | bpy.ops.object.transform_apply( 102 | location=self.location, rotation=self.rotation, scale=self.scale) 103 | 104 | for ob in linked_objects: 105 | ob.data = new_data 106 | if self.keep_visual: 107 | if self.location: 108 | ob.location += ob.matrix_basis.to_3x3() * location_factor 109 | if self.rotation: 110 | rot_mode_orig = ob.rotation_mode 111 | ob.rotation_mode = 'QUATERNION' 112 | ob.rotation_quaternion *= rot_factor 113 | ob.rotation_mode = rot_mode_orig 114 | if self.scale: 115 | ob.scale[0] *= scale_factor[0] 116 | ob.scale[1] *= scale_factor[1] 117 | ob.scale[2] *= scale_factor[2] 118 | ob.select = True 119 | 120 | if self.remove_original and orig_data.users == 0: 121 | context.blend_data.meshes.remove(orig_data) 122 | 123 | return {'FINISHED'} 124 | def invoke(self, context, event): 125 | return self.execute(context) 126 | 127 | class ApplyTransformMultiUserMenu(bpy.types.Menu): 128 | bl_label = "MultiUser" 129 | bl_idname = "VIEW3D_MT_object_apply_transform_multiuser" 130 | 131 | def draw(self, context): 132 | l = self.layout.operator( 133 | "object.apply_transform_multiuser", 134 | text="Location") 135 | l.location = True 136 | l.scale = False 137 | l.rotation = False 138 | 139 | r = self.layout.operator( 140 | "object.apply_transform_multiuser", 141 | text="Rotation") 142 | r.location = False 143 | r.scale = False 144 | r.rotation = True 145 | 146 | s = self.layout.operator( 147 | "object.apply_transform_multiuser", 148 | text="Scale") 149 | s.location = False 150 | s.scale = True 151 | s.rotation = False 152 | 153 | def draw_item(self, context): 154 | self.layout.menu(ApplyTransformMultiUserMenu.bl_idname) 155 | 156 | def register(): 157 | bpy.utils.register_class(ApplyTransformMultiUser) 158 | bpy.utils.register_class(ApplyTransformMultiUserMenu) 159 | bpy.types.VIEW3D_MT_object_apply.append(draw_item) 160 | 161 | def unregister(): 162 | bpy.utils.unregister_class(ApplyTransformMultiUser) 163 | bpy.utils.unregister_class(ApplyTransformMultiUserMenu) 164 | bpy.types.VIEW3D_MT_object_apply.remove(draw_item) 165 | 166 | if __name__ == '__main__': 167 | register() 168 | -------------------------------------------------------------------------------- /offset_unittest.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/underdoggit2/BlenderPythonScripts/9e688b8be56f7488fd03c94dc4151c8ee34aa817/offset_unittest.blend -------------------------------------------------------------------------------- /space_view3d_select_mode_pie.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name" : "Select Mode Pie Menu", 3 | "author" : "Stan Pancakes", 4 | "version" : (0, 1, 0), 5 | "blender" : (2, 72, 0), 6 | "description" : "Custom Pie Menus", 7 | "category" : "3D View",} 8 | 9 | import bpy 10 | 11 | class SelectModeCombined(bpy.types.Operator): 12 | bl_label = "Combined Select Mode" 13 | bl_idname = "edit.mesh_select_mode_combined" 14 | bl_options = {'INTERNAL'} 15 | 16 | mode = bpy.props.BoolVectorProperty(size=3) 17 | 18 | def execute(self, context): 19 | context.tool_settings.mesh_select_mode[:] = self.mode 20 | return {'FINISHED'} 21 | 22 | class MeshSelectMode(bpy.types.Menu): 23 | bl_label = "Select Mode" 24 | bl_idname = "VIEW3D_MT_edit_mesh_select_mode_pie" 25 | 26 | def draw(self, context): 27 | layout = self.layout 28 | pie = layout.menu_pie() 29 | 30 | # left 31 | pie.operator("edit.mesh_select_mode_combined", text='Vertex', icon='VERTEXSEL').mode=(True, False, False) 32 | # right 33 | pie.operator("edit.mesh_select_mode_combined", text='Face', icon='FACESEL').mode=(False, False, True) 34 | # bottom 35 | pie.operator("edit.mesh_select_mode_combined", text='Vertex & Edge & Face', icon='UV_FACESEL').mode=(True, True, True) 36 | # top 37 | pie.operator("edit.mesh_select_mode_combined", text='Edge', icon='EDGESEL').mode=(False, True, False) 38 | # topleft 39 | pie.operator("edit.mesh_select_mode_combined", text='Vertex & Edge', icon='UV_EDGESEL').mode=(True, True, False) 40 | # topright 41 | pie.operator("edit.mesh_select_mode_combined", text='Edge & Face', icon='SPACE2').mode=(False, True, True) 42 | # bottomleft 43 | pie.prop(context.space_data, "use_occlude_geometry", text='Limit to Visible') 44 | # bottomright 45 | pie.operator("edit.mesh_select_mode_combined", text='Vertex & Face', icon='LOOPSEL').mode=(True, False, True) 46 | 47 | def register(): 48 | bpy.utils.register_class(SelectModeCombined) 49 | bpy.utils.register_class(MeshSelectMode) 50 | 51 | def unregister(): 52 | bpy.utils.unregister_class(MeshSelectMode) 53 | bpy.utils.unregister_class(SelectModeCombined) 54 | -------------------------------------------------------------------------------- /space_view3d_set_smooth.py: -------------------------------------------------------------------------------- 1 | 2 | bl_info = { 3 | "name": "Set smooth menu", 4 | "author": "Hidesato Ikeya", 5 | "version": (0, 1), 6 | "blender": (2, 67, 0), 7 | "location": "View3D > Header", 8 | "description": "Set smooth menu on View3D header", 9 | "warning": "", 10 | "wiki_url": "", 11 | "tracker_url": "", 12 | "category": "3D View"} 13 | 14 | import bpy 15 | 16 | def menu_draw(self, context): 17 | #mode_string = context.mode 18 | row = self.layout.row(align=True) 19 | row.operator("object.shade_smooth", text='S') 20 | row.operator("object.shade_flat", text='F') 21 | 22 | act = context.active_object 23 | if act: 24 | self.layout.row().prop(act, 'show_wire', text='W') 25 | 26 | def register(): 27 | bpy.types.VIEW3D_HT_header.append(menu_draw) 28 | 29 | def unregister(): 30 | bpy.types.VIEW3D_HT_header.remove(menu_draw) 31 | 32 | 33 | if __name__ == '__main__': 34 | register() 35 | --------------------------------------------------------------------------------