├── obj_analiz_1D.py ├── chek_PointInObject.py ├── materials.py ├── anim_pencil.py ├── README.md ├── addon_add_point.py ├── SScale.py ├── PolyCamsRender.py ├── PolyCamsRender2.py ├── DimensionsPlus.py ├── PolyCamsRender_3.py ├── Auto_Perspective.py ├── Cross_pols.py ├── Viewer_hight.py ├── AutoExtrud.py ├── for_export_AssettoCorsa.py ├── PolyCamsRender4.py ├── mesh_gf_manager_13.py ├── F2_164.py ├── RetopoQuads.py ├── Paul_scrips.py ├── sverchok.py └── Doorway_134.py /obj_analiz_1D.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Alexander Nedovizin, 2013 4 | # version 0.1 5 | 6 | import bpy 7 | import bmesh 8 | 9 | scene = bpy.context.scene 10 | objects = scene.objects 11 | for o in objects: 12 | if o.type != 'MESH': 13 | continue 14 | 15 | me=o.data 16 | sym_split = '.P.' 17 | name_split = o.name.split(sym_split) 18 | if len(name_split)==2: 19 | o.name = name_split[0]+sym_split+str(len(me.polygons)) 20 | else: 21 | o.name = o.name+sym_split+str(len(me.polygons)) 22 | 23 | bm = bmesh.new() 24 | bm.from_mesh(me) 25 | 26 | true_edges=[] 27 | for face in bm.faces: 28 | true_edges.extend([e.index for e in face.edges]) 29 | 30 | flag=False 31 | for edge in bm.edges: 32 | if edge.index not in true_edges: 33 | flag=True 34 | if len(o.name)>1 and o.name[:2]=="$_": 35 | pass 36 | else: 37 | o.name = "$_"+o.name 38 | break 39 | 40 | if not flag and len(o.name)>2 and o.name[:2]=="$_": 41 | o.name = o.name[2:] 42 | 43 | bm.free() 44 | 45 | -------------------------------------------------------------------------------- /chek_PointInObject.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Alexander Nedovizin 3 | # A simple check on the entry point (Empty) into the object (Suzanne) 4 | 5 | import bpy 6 | import mathutils 7 | 8 | 9 | print('\n\n***** start *****') 10 | 11 | obj = bpy.context.scene.objects['Suzanne'] 12 | point = bpy.context.scene.objects['Empty'].location 13 | mesh = obj.data 14 | 15 | size = len(mesh.vertices) 16 | kd = mathutils.kdtree.KDTree(size) 17 | 18 | for i, p in enumerate(mesh.polygons): 19 | kd.insert(obj.matrix_local * p.center, i) 20 | 21 | kd.balance() 22 | 23 | flag = True 24 | # Далее находим ближайшие 10 точек в модели и проверим их на нормали 25 | for (co, index, dist) in kd.find_n(point, 10): 26 | p_no = mesh.polygons[index].normal 27 | pt_a = p_no + co 28 | cross_pt = mathutils.geometry.intersect_line_plane(pt_a, point, co, p_no) 29 | if cross_pt: 30 | pose_pt = mathutils.geometry.intersect_point_line(cross_pt, pt_a, point) 31 | if pose_pt[1]>1 or pose_pt[1]<0: 32 | flag = False 33 | break 34 | 35 | 36 | 37 | if flag: 38 | print('is_boundary') 39 | else: 40 | print('not is_boundary') 41 | 42 | -------------------------------------------------------------------------------- /materials.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | import bpy 4 | 5 | 6 | def saveMat(name): 7 | if name not in bpy.data.materials: 8 | return False 9 | mat = bpy.data.materials[name] 10 | Mat = {} 11 | for m in dir(mat): 12 | prop = eval('bpy.data.materials[name].'+m) 13 | typ = type(prop) 14 | if typ in [int, float, bool, str]: 15 | if m =='name': 16 | continue 17 | Mat[m]=prop 18 | #print('^^^ save mat ^^^',m,Mat[m]) 19 | 20 | 21 | if str(typ)==(""): 22 | Mat[m]=list(prop) 23 | #print('>> save mat Color >>',str(m),Mat[str(m)]) 24 | 25 | 26 | saveData = json.dumps(Mat) 27 | file = open("newfile.txt", "w") 28 | file.write(saveData) 29 | return saveData 30 | 31 | 32 | def loadMat(name): 33 | if name not in bpy.data.materials: 34 | print(False) 35 | return False 36 | mat = bpy.data.materials[name] 37 | 38 | file = open('newfile.txt', 'r') 39 | Mats=file.read() 40 | 41 | Mat = json.loads(Mats) 42 | 43 | dir_mat = dir(bpy.data.materials[name]) 44 | for m in Mat.items(): 45 | if m[0]not in dir_mat: 46 | continue 47 | 48 | prop_dest = eval('bpy.data.materials[name]') 49 | 50 | try: 51 | prop_dest.__setattr__(m[0],m[1]) 52 | #print('!!! load !!!!!!!!!',m[0], m[1]) 53 | except: 54 | #print('!!! only read',m[0], m[1]) 55 | pass 56 | 57 | 58 | 59 | 60 | return True 61 | 62 | 63 | 64 | 65 | print('\n\n*************') 66 | 67 | MM = saveMat('Material.002') 68 | #print('MM',type(MM)) 69 | loadMat('Material') 70 | -------------------------------------------------------------------------------- /anim_pencil.py: -------------------------------------------------------------------------------- 1 | # Nedovizin Alexander 2 | 3 | import bpy 4 | import math 5 | from mathutils import Vector 6 | 7 | 8 | def MakePolyLine(objname, curvename, cList): 9 | w = 1 # weight 10 | curvedata = bpy.data.curves.new(name=curvename, type='CURVE') 11 | curvedata.dimensions = '3D' 12 | 13 | objectdata = bpy.data.objects.new(objname, curvedata) 14 | objectdata.location = (0,0,0) #object origin 15 | bpy.context.scene.objects.link(objectdata) 16 | 17 | polyline = curvedata.splines.new('NURBS') 18 | polyline.points.add(len(cList)-1) 19 | for num in range(len(cList)): 20 | x, y, z = cList[num] 21 | polyline.points[num].co = (x, y, z, w) 22 | 23 | polyline.order_u = len(polyline.points)-1 24 | polyline.use_endpoint_u = True 25 | return curvedata 26 | 27 | 28 | points=[] 29 | start_frame=1 30 | word_frames = 30 31 | 32 | for gp in bpy.data.grease_pencil: 33 | for gpl in gp.layers: 34 | for idx, stroke in enumerate(gpl.active_frame.strokes): 35 | for p in stroke.points: 36 | points.append(Vector(p.co)) 37 | 38 | word_frames = len(stroke.points) 39 | polyline = MakePolyLine("NameOfMyCurveObject", "NameOfMyCurve", points) 40 | points=[] 41 | 42 | print(type(polyline)) 43 | polyline.bevel_depth = 0.02 44 | 45 | polyline.bevel_factor_end = 0 46 | polyline.keyframe_insert(data_path='bevel_factor_end', frame = start_frame) 47 | start_frame += word_frames 48 | polyline.bevel_factor_end = 1 49 | polyline.keyframe_insert(data_path='bevel_factor_end', frame = start_frame) 50 | 51 | bpy.context.scene.frame_end = start_frame + 100 52 | 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Scripts 2 | ======= 3 | 4 | Scripts by Alexandr Nedovizn 5 | 6 | 1D_Scripts.py - CADTools 7 | 8 | AutoExtrud.py - выдавливание профиля по образцу с учетом материалов 9 | 10 | Doorway_134.py - делает дверные проёмы в в стене методом boolian. http://www.youtube.com/watch?v=fuGKTTyY4Mo 11 | 12 | DimensionsPlus.py - добавляет возможность задавать зависимые размеры в раздел Dimensions 13 | 14 | Cross_pols.py - различные режимы работы с меша с рабочей плоскостью: рассчечение, срезы, частичное выделение выше/ниже плоскости, пересекаемые ребра 15 | 16 | chek_PointInObject.py - проверка вхождения точки в объект 17 | 18 | F2_164.py - адон F2 with autograb-function 19 | 20 | Paul_scrips.py - распределение вершин по кривой с различными вариантами выравнивания 21 | 22 | PolyCamsRender.py - рендер с разных камер. нативный вариант 23 | 24 | PolyCamsRender2.py - неудачная попытка реализации PolyCamsRender.py без зависания блендера 25 | 26 | PolyCamsRender_3.py - рендер с раных камер, но с учетом меток в TimeLine. Т.е. можно рендерить не всю анимацию, а кусками, расставив соответствующие метки. Допускается использовать метки с любым именем, но метка 'end' прерывает анимацию. Через несколько кадров новая метка начинает новую серию кадров. И т.д. 27 | 28 | PolyCamsRender4.py - рендер с разных камер, но с логами о статистике и ходе рендера. Следите за файлами stat.txt и log.txt - в тойже папке, куда рендерится. 29 | 30 | RetopoQuads.py - ретопология стен. Автоматическая. http://www.youtube.com/watch?v=yR6KPMZoywc 31 | 32 | SScale.py - масштабировать в нуль по оси Х или У. Автоматически. 33 | 34 | Viewer_hight.py - цифровое обозначение высот 35 | 36 | anim_pencil.py - анимирует нарисованное карандашом. http://www.youtube.com/watch?v=Cpnv6feClFQ 37 | 38 | addon_add_point.py - добавляет точку в список Shift+A -> Mesh. Удобно в моделировании. 39 | 40 | for_export_AssettoCorsa.py - дополняет экспорт материалами 41 | 42 | materials.py - копирует материалы одного объекта на другой через JSON-файл 43 | 44 | mesh_gf_manager_13.py - наш незабываемый Grid Fill Manager 45 | 46 | obj_analiz_1D.py - переименование объектов в соответствии с информацией о поликах 47 | 48 | sverchok.py - зародыш 49 | 50 | Auto_Perspective.py - автоматическое переключение перспективы. Active buttons: Numpad 1,3,7 - ORTHO and mouse_middlebutton - PERSP 51 | -------------------------------------------------------------------------------- /addon_add_point.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name": "New Object", 3 | "author": "Alexander Nedovizin", 4 | "version": (1, 0), 5 | "blender": (2, 65, 0), 6 | "location": "View3D > Add > Mesh > New Object", 7 | "description": "Adds a new Mesh Object", 8 | "warning": "", 9 | "wiki_url": "", 10 | "category": "Add Mesh"} 11 | 12 | 13 | import bpy 14 | from bpy.types import Operator 15 | from bpy.props import FloatVectorProperty 16 | from bpy_extras.object_utils import AddObjectHelper, object_data_add 17 | from mathutils import Vector 18 | 19 | 20 | def add_object(self, context): 21 | verts = [Vector((0,0,0))] 22 | 23 | edges = [] 24 | faces = [] 25 | 26 | mesh = bpy.data.meshes.new(name="New Object Point") 27 | mesh.from_pydata(verts, edges, faces) 28 | # useful for development when the mesh may be invalid. 29 | # mesh.validate(verbose=True) 30 | object_data_add(context, mesh, operator=self) 31 | 32 | 33 | class OBJECT_OT_add_object(Operator, AddObjectHelper): 34 | """Create a new Mesh Point""" 35 | bl_idname = "mesh.add_object" 36 | bl_label = "Add Object Point" 37 | bl_options = {'REGISTER', 'UNDO'} 38 | 39 | 40 | def execute(self, context): 41 | 42 | add_object(self, context) 43 | 44 | return {'FINISHED'} 45 | 46 | 47 | # Registration 48 | 49 | def add_object_button(self, context): 50 | self.layout.operator( 51 | OBJECT_OT_add_object.bl_idname, 52 | text="Add Point", 53 | icon='LAYER_ACTIVE') 54 | 55 | 56 | # This allows you to right click on a button and link to the manual 57 | def add_object_manual_map(): 58 | url_manual_prefix = "http://wiki.blender.org/index.php/Doc:2.6/Manual/" 59 | url_manual_mapping = ( 60 | ("bpy.ops.mesh.add_object", "Modeling/Objects"), 61 | ) 62 | return url_manual_prefix, url_manual_mapping 63 | 64 | 65 | def register(): 66 | bpy.utils.register_class(OBJECT_OT_add_object) 67 | bpy.utils.register_manual_map(add_object_manual_map) 68 | bpy.types.INFO_MT_mesh_add.append(add_object_button) 69 | 70 | 71 | def unregister(): 72 | bpy.utils.unregister_class(OBJECT_OT_add_object) 73 | bpy.utils.unregister_manual_map(add_object_manual_map) 74 | bpy.types.INFO_MT_mesh_add.remove(add_object_button) 75 | 76 | 77 | if __name__ == "__main__": 78 | register() 79 | 80 | -------------------------------------------------------------------------------- /SScale.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Author: Nedovizin Alexander, 19.09.2013 4 | 5 | 6 | bl_info = { 7 | "name": "SScale", 8 | "author": "Alexander Nedovizin", 9 | "version": (0, 1, 0), 10 | "blender": (2, 6, 8), 11 | "location": "View3D > Toolbar", 12 | "category": "Mesh" 13 | } 14 | 15 | 16 | 17 | 18 | import bpy 19 | 20 | 21 | def find_index_of_selected_vertex(mesh): 22 | selected_verts = [i.index for i in mesh.vertices if i.select] 23 | verts_selected = len(selected_verts) 24 | if verts_selected <1: 25 | return None 26 | else: 27 | return selected_verts 28 | 29 | 30 | def main(context): 31 | obj = bpy.context.active_object 32 | me = obj.data 33 | 34 | bpy.ops.object.mode_set(mode='OBJECT') 35 | bpy.ops.object.mode_set(mode='EDIT') 36 | 37 | vs_idx = find_index_of_selected_vertex(me) 38 | if vs_idx: 39 | x_coos = [v.co.x for v in me.vertices if v.index in vs_idx] 40 | y_coos = [v.co.y for v in me.vertices if v.index in vs_idx] 41 | 42 | min_x = min(x_coos) 43 | max_x = max(x_coos) 44 | 45 | min_y = min(y_coos) 46 | max_y = max(y_coos) 47 | 48 | len_x = max_x-min_x 49 | len_y = max_y-min_y 50 | 51 | if len_y Toolbar", 7 | "description": "Proportional dimensions", 8 | "tracker_url": "https://github.com/Cfyzzz/Scripts/blob/master/DimensionsPlus.py", 9 | "category": "Object"} 10 | 11 | 12 | import bpy 13 | from bpy.props import * 14 | 15 | class ElviraPanel(bpy.types.Panel): 16 | bl_label = "Dimensions plus" 17 | bl_space_type = "VIEW_3D" 18 | bl_region_type = "UI" 19 | 20 | @classmethod 21 | def poll(cls, context): 22 | return context.active_object is not None and context.active_object.type=='MESH' 23 | 24 | 25 | def draw(self, context): 26 | lt = context.object.data 27 | layout = self.layout 28 | row = layout.row(align=True) 29 | if (lt.prop_x==-1)or(lt.prop_y==-1)or(lt.prop_z==-1): 30 | row.operator('elvira.copydims', text = 'Copy') 31 | check_prop = False 32 | else: 33 | row.operator('elvira.pastedims', text = 'Confirm') 34 | check_prop = True 35 | 36 | row = layout.row() 37 | col = row.column(align=True) 38 | row.enabled = check_prop 39 | subrow = col.row(align=True) 40 | subrow.prop(lt, 'prop_x', text = 'X') 41 | subrow = col.row(align=True) 42 | subrow.prop(lt, 'prop_y', text = 'Y') 43 | subrow = col.row(align=True) 44 | subrow.prop(lt, 'prop_z', text = 'Z') 45 | 46 | col = row.column(align=True) 47 | row = col.row(align=True) 48 | row.prop(lt, 'check_x', text = '', icon='LINKED') 49 | row = col.row(align=True) 50 | row.prop(lt, 'check_y', text = '', icon='LINKED') 51 | row = col.row(align=True) 52 | row.prop(lt, 'check_z', text = '', icon='LINKED') 53 | 54 | 55 | class OBJECT_OT_copyclick(bpy.types.Operator): 56 | bl_idname = "elvira.copydims" 57 | bl_label = "Copy dimensions" 58 | 59 | def execute(self, context): 60 | lt = context.object.data 61 | lt.prop_x, lt.prop_y, lt.prop_z=context.object.dimensions 62 | return{'FINISHED'} 63 | 64 | 65 | class OBJECT_OT_pasteclick(bpy.types.Operator): 66 | bl_idname = "elvira.pastedims" 67 | bl_label = "Paste dimensions" 68 | 69 | def execute(self, context): 70 | lt = context.object.data 71 | l1 = [lt.prop_x, lt.prop_y, lt.prop_z] 72 | l2=list(context.object.dimensions) 73 | l3 = [lt.check_x, lt.check_y, lt.check_z] 74 | 75 | for idx,l in enumerate(l1): 76 | if l!=l2[idx]: 77 | if l3[idx]: 78 | k = l/l2[idx] 79 | else: 80 | k = 1 81 | 82 | context.object.dimensions = list(map\ 83 | (lambda x,y,z:x*k if y==True else z, l2,l3,l1)) 84 | bpy.ops.elvira.copydims() 85 | break 86 | 87 | return{'FINISHED'} 88 | 89 | def initialize(): 90 | bpy.types.Mesh.prop_x = FloatProperty(name="prop_x", default=-1, precision=3) 91 | bpy.types.Mesh.prop_y = FloatProperty(name="prop_y", default=-1, precision=3) 92 | bpy.types.Mesh.prop_z = FloatProperty(name="prop_z", default=-1, precision=3) 93 | bpy.types.Mesh.check_x = BoolProperty(name="check_x", default = True) 94 | bpy.types.Mesh.check_y = BoolProperty(name="check_y", default = True) 95 | bpy.types.Mesh.check_z = BoolProperty(name="check_z", default = True) 96 | 97 | 98 | classes = [ElviraPanel, OBJECT_OT_copyclick, OBJECT_OT_pasteclick] 99 | def register(): 100 | for c in classes: 101 | bpy.utils.register_class(c) 102 | initialize() 103 | 104 | 105 | def unregister(): 106 | del bpy.types.WindowManager.elvira_manager 107 | for c in reversed(classes): 108 | bpy.utils.unregister_class(c) 109 | 110 | if __name__ == "__main__": 111 | register() 112 | 113 | -------------------------------------------------------------------------------- /PolyCamsRender_3.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy_extras.io_utils import ExportHelper 3 | from bpy.props import StringProperty, BoolProperty, EnumProperty 4 | from bpy.types import Operator 5 | from copy import copy 6 | 7 | bl_info = { 8 | "name": "PolyCams Render", 9 | "author": "Alexander Nedovizin", 10 | "version": (0, 3, 0), 11 | "blender": (2, 6, 7), 12 | "category": "Render" 13 | } 14 | 15 | 16 | class ExportSomeData(Operator, ExportHelper): 17 | """This appears in the tooltip of the operator and in the generated docs""" 18 | bl_idname = "export_test.some_data" # important since its how bpy.ops.import_test.some_data is constructed 19 | bl_label = "Export Some Data" 20 | 21 | # ExportHelper mixin class uses this 22 | filename_ext = "" 23 | 24 | def execute(self, context): 25 | return render_me(self.filepath) 26 | 27 | 28 | def render_me(filepath): 29 | sceneName = bpy.context.scene.name 30 | scene = bpy.data.scenes[sceneName] 31 | marks = scene.timeline_markers 32 | 33 | lm=[[m.frame, i] for i,m in enumerate(marks)] 34 | lm.sort() 35 | diap = [] 36 | print('lm',lm) 37 | 38 | for idx, l_mark in enumerate(lm[:-1]): 39 | mark = marks[l_mark[1]] 40 | if mark.name == "end": 41 | continue 42 | 43 | start_fr = mark.frame 44 | if (len(marks)-1)>idx: 45 | end_fr = marks[lm[idx+1][1]].frame 46 | else: 47 | end_fr = copy(scene.frame_end) 48 | 49 | name_fr = mark.name 50 | diap.append((start_fr, end_fr, name_fr)) 51 | 52 | if len(diap)==0: 53 | diap.append((scene.frame_start, scene.frame_end, '')) 54 | 55 | for d in diap: 56 | scene.frame_start = d[0] 57 | scene.frame_end = d[1] 58 | for cam in bpy.data.objects: 59 | if ( cam.type =='CAMERA' and not cam.hide_render): 60 | bpy.data.scenes[sceneName].camera = cam 61 | if d[2]!='': 62 | bpy.data.scenes[sceneName].render.filepath = filepath+'\\'+d[2]+'\\'+cam.name+'\\'+d[2] 63 | else: 64 | bpy.data.scenes[sceneName].render.filepath = filepath+'\\'+cam.name+'\\'+cam.name 65 | bpy.ops.render.render(animation=True) 66 | 67 | print('Done!') 68 | #print(bpy.data.scenes[sceneName].render.filepath) 69 | return {'FINISHED'} 70 | 71 | 72 | class RenderMe(bpy.types.Operator): 73 | """Cams render""" 74 | bl_idname = "scene.render_me" 75 | bl_label = "Render Me" 76 | bl_options = {'REGISTER', 'UNDO'} 77 | 78 | def execute(self, context): 79 | bpy.ops.export_test.some_data('INVOKE_DEFAULT') 80 | return {'FINISHED'} 81 | 82 | 83 | def menu_func(self, context): 84 | self.layout.operator(RenderMe.bl_idname) 85 | def menu_func_export(self, context): 86 | self.layout.operator(ExportSomeData.bl_idname, text="Cams Render!") 87 | 88 | 89 | addon_keymaps = [] 90 | 91 | def register(): 92 | bpy.utils.register_class(RenderMe) 93 | bpy.types.VIEW3D_MT_object.append(menu_func) 94 | 95 | bpy.utils.register_class(ExportSomeData) 96 | bpy.types.INFO_MT_file_export.append(menu_func_export) 97 | 98 | # handle the keymap 99 | wm = bpy.context.window_manager 100 | km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY') 101 | kmi = km.keymap_items.new(RenderMe.bl_idname, 'F12', 'PRESS', ctrl=True, shift=False) 102 | addon_keymaps.append((km, kmi)) 103 | 104 | def unregister(): 105 | bpy.utils.unregister_class(RenderMe) 106 | bpy.types.VIEW3D_MT_object.remove(menu_func) 107 | 108 | bpy.utils.unregister_class(ExportSomeData) 109 | bpy.types.INFO_MT_file_export.remove(menu_func_export) 110 | 111 | # handle the keymap 112 | for km, kmi in addon_keymaps: 113 | km.keymap_items.remove(kmi) 114 | addon_keymaps.clear() 115 | 116 | 117 | if __name__ == "__main__": 118 | register() 119 | bpy.ops.scene.render_me() -------------------------------------------------------------------------------- /Auto_Perspective.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # ##### BEGIN GPL LICENSE BLOCK ##### 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | 22 | bl_info = { 23 | "name": "Auto Perspective", 24 | "author": "Alexander Nedovizin", 25 | "version": (0, 1, 0), 26 | "blender": (2, 7, 0), 27 | "location": "View3D > Auto Perspective", 28 | "category": "3D View" 29 | } 30 | 31 | import bpy 32 | 33 | 34 | 35 | def main(context): 36 | pass 37 | return 38 | 39 | class ModalTimerOperator(bpy.types.Operator): 40 | """Operator which runs its self from a timer""" 41 | bl_idname = "wm.modal_timer_operator" 42 | bl_label = "Start AutoPersp" 43 | 44 | _timer = None 45 | frequence = bpy.props.FloatProperty(name="Frequence", default=1.0) 46 | mode = bpy.props.BoolProperty(name="Mode", default=False) 47 | 48 | @classmethod 49 | def poll(cls, context): 50 | return (context.area.type == 'VIEW_3D') 51 | 52 | def modal(self, context, event): 53 | config = bpy.context.window_manager.CONFIG_Z 54 | if event.type == 'ESC' or config.label == 'Start AutoPersp': 55 | return self.cancel(context) 56 | 57 | if event.type == 'TIMER': 58 | main(context) 59 | 60 | if event.type in ['NUMPAD_1', 'NUMPAD_3', 'NUMPAD_7']: 61 | view3d = context.space_data.region_3d 62 | view3d.view_perspective = 'ORTHO' 63 | view3d.update() 64 | self.mode = True 65 | 66 | if event.type == 'MIDDLEMOUSE': 67 | view3d = context.space_data.region_3d 68 | if self.mode: 69 | view3d.view_perspective = 'PERSP' 70 | view3d.update() 71 | self.mode = False 72 | 73 | return {'PASS_THROUGH'} 74 | 75 | def invoke(self, context, event): 76 | self._timer = context.window_manager.event_timer_add(self.frequence, context.window) 77 | context.window_manager.modal_handler_add(self) 78 | config = bpy.context.window_manager.CONFIG_Z 79 | if config.label == 'Start AutoPersp': 80 | config.label = 'Stop AutoPersp' 81 | else: 82 | config.label = 'Start AutoPersp' 83 | return {'RUNNING_MODAL'} 84 | 85 | def cancel(self, context): 86 | context.window_manager.event_timer_remove(self._timer) 87 | config = bpy.context.window_manager.CONFIG_Z 88 | config.label = 'Start AutoPersp' 89 | return {'FINISHED'} 90 | 91 | 92 | class HelloWorldPanel(bpy.types.Panel): 93 | """Creates a Panel in the Object properties window""" 94 | bl_label = "Auto Perspective" 95 | bl_space_type = 'VIEW_3D' 96 | bl_region_type = 'TOOLS' 97 | 98 | 99 | def draw(self, context): 100 | config = bpy.context.window_manager.CONFIG_Z 101 | 102 | layout = self.layout 103 | col = layout.column(align=True) 104 | 105 | split = col.split(percentage=0.15) 106 | if config.display: 107 | split.prop(config, "display", text="", icon='DOWNARROW_HLT') 108 | else: 109 | split.prop(config, "display", text="", icon='RIGHTARROW') 110 | 111 | timer_op = split.operator("wm.modal_timer_operator", text = config.label) 112 | timer_op.frequence = config.frequence 113 | 114 | if config.display: 115 | box = col.column(align=True).box().column() 116 | col_top = box.column(align=True) 117 | row = col_top.row(align=True) 118 | row.prop(config,'frequence') 119 | 120 | 121 | 122 | class UIElements(bpy.types.PropertyGroup): 123 | frequence = bpy.props.FloatProperty(name="Frequence") 124 | label = bpy.props.StringProperty(name="label") 125 | display = bpy.props.BoolProperty(name="display") 126 | 127 | 128 | def register(): 129 | bpy.utils.register_class(HelloWorldPanel) 130 | bpy.utils.register_class(ModalTimerOperator) 131 | bpy.utils.register_class(UIElements) 132 | bpy.types.WindowManager.CONFIG_Z = bpy.props.PointerProperty(type = UIElements) 133 | bpy.context.window_manager.CONFIG_Z.frequence = 1.0 134 | bpy.context.window_manager.CONFIG_Z.label = 'Start AutoPersp' 135 | bpy.context.window_manager.CONFIG_Z.display = False 136 | 137 | 138 | 139 | def unregister(): 140 | del bpy.types.WindowManager.CONFIG_Z 141 | bpy.utils.unregister_class(UIElements) 142 | bpy.utils.unregister_class(ModalTimerOperator) 143 | bpy.utils.unregister_class(HelloWorldPanel) 144 | 145 | 146 | if __name__ == "__main__": 147 | register() 148 | 149 | -------------------------------------------------------------------------------- /Cross_pols.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Author: Alexander Nedovizin 4 | # name script: Cross_pols.py 5 | # version: 0.6 6 | 7 | import bpy 8 | import mathutils 9 | from mathutils import Vector 10 | import bmesh 11 | 12 | SPLIT = False # SPLIT/CROSS режим включается когда остальные выключены 13 | inner_clear = False # опция режима SPLIT/CROSS 14 | outer_clear = False # опция режима SPLIT/CROSS 15 | fill_cuts = False # опция режима SPLIT/CROSS 16 | 17 | filter_edges = False # Режим пересеченных ребер. включается, когда остальные режимы выключены 18 | 19 | filter_verts_top = False # Режим выделения вершин и его Опция верхняя половина (по нормали секатора) 20 | filter_verts_bottom = False # Режим выделения вершин и его Опция нижняя половина (по нормали секатора) 21 | 22 | # http://dl.dropboxusercontent.com/u/59609328/Blender-Rus/Cross_pols.py 23 | 24 | 25 | def main(): 26 | obj = bpy.context.active_object 27 | if obj.type != 'MESH': 28 | return 29 | 30 | bpy.ops.object.mode_set(mode='OBJECT') 31 | bpy.ops.object.mode_set(mode='EDIT') 32 | bpy.ops.mesh.select_mode(type='FACE') 33 | 34 | me = obj.data 35 | 36 | 37 | bm = bmesh.new() 38 | bm.from_mesh(me) 39 | 40 | P1 = me.polygons[bm.faces.active.index] 41 | pols = [p.index for p in me.polygons if p.select and p.index!= P1.index] 42 | sel_edges = [] 43 | sel_verts = [] 44 | vts_all = [v for v in bm.verts if v.select and v.index not in P1.vertices] 45 | eds_all = [e for e in bm.edges if e.select and e.verts[0].index not in P1.vertices \ 46 | and e.verts[1].index not in P1.vertices] 47 | 48 | if not filter_verts_top and not filter_verts_bottom and not filter_edges: 49 | p1_co = me.vertices[P1.vertices[0]].co 50 | p1_no = P1.normal 51 | for pol in pols: 52 | P2 = me.polygons[pol] 53 | p2_co = me.vertices[P2.vertices[0]].co 54 | p2_no = P2.normal 55 | 56 | cross_line = mathutils.geometry.intersect_plane_plane(p1_co, p1_no, p2_co, p2_no) 57 | points = [] 58 | split_ed = [] 59 | for idx, edg in enumerate(P2.edge_keys): 60 | pt_a = me.vertices[edg[0]].co 61 | pt_b = me.vertices[edg[1]].co 62 | cross_pt = mathutils.geometry.intersect_line_plane(pt_a, pt_b, p1_co, p1_no) 63 | if cross_pt: 64 | pose_pt = mathutils.geometry.intersect_point_line(cross_pt, pt_a, pt_b) 65 | if pose_pt[1]<=1 and pose_pt[1]>=0: 66 | points.append(pose_pt[0]) 67 | split_ed.append(idx) 68 | 69 | 70 | if len(points)==2: 71 | bpy.ops.mesh.select_mode(type='VERT') 72 | if not SPLIT: 73 | v1=bm.verts.new(points[0]) 74 | v2=bm.verts.new(points[1]) 75 | bm.verts.index_update() 76 | edge = (v1,v2) 77 | edg_i = bm.edges.new(edge) 78 | sel_edges.append(edg_i) 79 | else: 80 | """ Функция позаимствована из адона Сверчок нод Bisect """ 81 | verts4cut = vts_all 82 | edges4cut = eds_all 83 | faces4cut = [fa for fa in bm.faces if fa.index in pols] 84 | edges4cut_idx = [ed.index for ed in eds_all] 85 | 86 | geom_in = verts4cut + edges4cut + faces4cut 87 | res = bmesh.ops.bisect_plane(bm, geom=geom_in, dist=0.00001, 88 | plane_co=p1_co, plane_no=p1_no, use_snap_center=False, 89 | clear_outer=outer_clear, clear_inner=inner_clear) 90 | 91 | fres = bmesh.ops.edgenet_prepare(bm, edges=[e for e in res['geom_cut'] 92 | if isinstance(e, bmesh.types.BMEdge)]) 93 | 94 | sel_edges = [e for e in fres['edges'] if e.index not in edges4cut_idx] 95 | 96 | # this needs work function with solid gemometry 97 | if fill_cuts: 98 | fres = bmesh.ops.edgenet_prepare(bm, edges=[e for e in res['geom_cut'] 99 | if isinstance(e, bmesh.types.BMEdge)]) 100 | bmesh.ops.edgeloop_fill(bm, edges=fres['edges']) 101 | 102 | bm.verts.index_update() 103 | bm.edges.index_update() 104 | bm.faces.index_update() 105 | break 106 | 107 | if filter_verts_top or filter_verts_bottom: 108 | bpy.ops.mesh.select_mode(type='VERT') 109 | p1_co = me.vertices[P1.vertices[0]].co 110 | p1_no = P1.normal 111 | for v in vts_all: 112 | res = mathutils.geometry.distance_point_to_plane(v.co, p1_co, p1_no) 113 | if res>=0: 114 | if filter_verts_top: 115 | sel_verts.append(v) 116 | else: 117 | if filter_verts_bottom: 118 | sel_verts.append(v) 119 | 120 | if filter_edges and not filter_verts_top and not filter_verts_bottom: 121 | bpy.ops.mesh.select_mode(type='EDGE') 122 | p1_co = me.vertices[P1.vertices[0]].co 123 | p1_no = P1.normal 124 | print(eds_all) 125 | for idx, edg in enumerate(eds_all): 126 | pt_a = edg.verts[0].co 127 | pt_b = edg.verts[1].co 128 | cross_pt = mathutils.geometry.intersect_line_plane(pt_a, pt_b, p1_co, p1_no) 129 | if cross_pt: 130 | pose_pt = mathutils.geometry.intersect_point_line(cross_pt, pt_a, pt_b) 131 | if pose_pt[1]<=1 and pose_pt[1]>=0: 132 | sel_edges.append(edg) 133 | 134 | bm.edges.index_update() 135 | for v in bm.verts: 136 | v.select_set(False) 137 | bm.select_flush(False) 138 | for ed in sel_edges: 139 | ed.select=True 140 | for ed in sel_verts: 141 | ed.select=True 142 | 143 | bpy.ops.object.mode_set(mode='OBJECT') 144 | bm.to_mesh(me) 145 | me.update() 146 | bm.free() 147 | bpy.ops.object.mode_set(mode='EDIT') 148 | 149 | 150 | main() -------------------------------------------------------------------------------- /Viewer_hight.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*-  2 | 3 | bl_info = { 4 | "name": "Viewer Hights", 5 | "author": "Alexander Nedovizin", 6 | "version": (0, 1, 4), 7 | "blender": (2, 6, 8), 8 | "location": "View3D > Viewer Hights", 9 | "category": "3D View" 10 | } 11 | 12 | import bpy 13 | import random, mathutils 14 | from mathutils import Matrix 15 | 16 | def align_matrix(locat): 17 | context = bpy.context 18 | loc = Matrix.Translation(locat) 19 | print('Type',context.space_data.type) 20 | 21 | if (context.space_data.type == 'VIEW_3D'): 22 | print('align') 23 | rot = context.space_data.region_3d.view_matrix.to_3x3().inverted().to_4x4() 24 | else: 25 | rot = Matrix() 26 | align_matrix = loc * rot 27 | return align_matrix 28 | 29 | 30 | 31 | def main(context): 32 | config = bpy.data.scenes[0].CONFIG_Z 33 | scene = bpy.context.scene 34 | object = bpy.context.object 35 | mesh = object.data 36 | texts = [] 37 | if object.name[:6] == 'Number': 38 | return 39 | 40 | mode_o = object.mode 41 | bpy.ops.object.mode_set(mode='OBJECT') 42 | bpy.ops.object.mode_set(mode='EDIT') 43 | bpy.ops.object.mode_set(mode=mode_o) 44 | 45 | for v in mesh.vertices: 46 | name_text = 'Number'+str(v.index) 47 | if not v.select : 48 | name_text = 'Number'+str(v.index) 49 | if name_text in bpy.data.objects: 50 | bpy.data.objects[name_text].hide=True 51 | continue 52 | 53 | if name_text not in bpy.data.objects: 54 | textData = bpy.data.curves.new('Number','FONT') 55 | textData.size = config.size / 100 56 | name_text = 'Number'+str(v.index) 57 | textOb = bpy.data.objects.new(name_text, textData) 58 | scene.objects.link(textOb) 59 | texts.append(textOb) 60 | else: 61 | textOb = bpy.data.objects[name_text] 62 | textOb.hide=False 63 | textOb.matrix_world = align_matrix(v.co) 64 | textOb.data.body = str(round((object.matrix_local*textOb.location).z,3)) 65 | #textOb.location += mathutils.Vector((0.1,0.1,0.1)) 66 | textOb.show_x_ray = config.x_ray 67 | textOb.matrix_world = object.matrix_local * textOb.matrix_world 68 | 69 | mat = config.mat 70 | if mat in bpy.data.materials: 71 | if len(textOb.material_slots)>0: 72 | textOb.material_slots[0].material = bpy.data.materials[mat] 73 | else: 74 | textOb.select = True 75 | tmpObj = scene.objects.active 76 | scene.objects.active = textOb 77 | bpy.ops.object.material_slot_add() 78 | textOb.material_slots[0].material = bpy.data.materials[mat] 79 | scene.objects.active = tmpObj 80 | textOb.select = False 81 | 82 | 83 | class ModalTimerOperator(bpy.types.Operator): 84 | """Operator which runs its self from a timer""" 85 | bl_idname = "wm.modal_timer_operator" 86 | bl_label = "Start View Hight" 87 | 88 | _timer = None 89 | frequence = bpy.props.FloatProperty(name="Frequence", default=1.0) 90 | 91 | def modal(self, context, event): 92 | if event.type == 'ESC': 93 | return self.cancel(context) 94 | 95 | if event.type == 'TIMER': 96 | main(context) 97 | 98 | return {'PASS_THROUGH'} 99 | 100 | def execute(self, context): 101 | self._timer = context.window_manager.event_timer_add(self.frequence, context.window) 102 | context.window_manager.modal_handler_add(self) 103 | return {'RUNNING_MODAL'} 104 | 105 | def cancel(self, context): 106 | context.window_manager.event_timer_remove(self._timer) 107 | object = bpy.context.object 108 | mode_o = object.mode 109 | bpy.ops.object.mode_set(mode='OBJECT') 110 | bpy.ops.object.hide_view_clear() 111 | bpy.ops.object.select_all(action='DESELECT') 112 | bpy.ops.object.select_pattern(pattern='Number*') 113 | bpy.ops.object.delete() 114 | bpy.ops.object.mode_set(mode=mode_o) 115 | object.select = True 116 | bpy.context.scene.objects.active = object 117 | return {'CANCELLED'} 118 | 119 | 120 | 121 | 122 | 123 | class HelloWorldPanel(bpy.types.Panel): 124 | """Creates a Panel in the Object properties window""" 125 | bl_label = "Viewer Hight" 126 | bl_idname = "OBJECT_PT_hello" 127 | bl_space_type = 'VIEW_3D' 128 | bl_region_type = 'TOOLS' 129 | 130 | def draw(self, context): 131 | config = bpy.data.scenes[0].CONFIG_Z 132 | 133 | layout = self.layout 134 | col = layout.column(align=True) 135 | 136 | split = col.split(percentage=0.15) 137 | if config.display: 138 | split.prop(config, "display", text="", icon='DOWNARROW_HLT') 139 | else: 140 | split.prop(config, "display", text="", icon='RIGHTARROW') 141 | 142 | timer_op = split.operator("wm.modal_timer_operator") 143 | timer_op.frequence = config.frequence 144 | 145 | if config.display: 146 | box = col.column(align=True).box().column() 147 | col_top = box.column(align=True) 148 | row = col_top.row(align=True) 149 | row.prop(config,'frequence') 150 | row = col_top.row(align=True) 151 | row.prop(config,'x_ray') 152 | row = col_top.row(align=True) 153 | row.prop(config,'size') 154 | row = col_top.row(align=True) 155 | row.prop(config,'mat') 156 | 157 | 158 | 159 | 160 | 161 | 162 | class UIElements(bpy.types.PropertyGroup): 163 | frequence = bpy.props.FloatProperty(name="Frequence") 164 | x_ray = bpy.props.BoolProperty(name="X-ray") 165 | size = bpy.props.IntProperty(name="Size") 166 | mat = bpy.props.StringProperty(name="Material") 167 | display = bpy.props.BoolProperty(name="display") 168 | 169 | 170 | def register(): 171 | bpy.utils.register_class(HelloWorldPanel) 172 | bpy.utils.register_class(ModalTimerOperator) 173 | bpy.utils.register_class(UIElements) 174 | bpy.types.Scene.CONFIG_Z = bpy.props.PointerProperty(type = UIElements) 175 | bpy.data.scenes[0].CONFIG_Z.frequence = 1.0 176 | bpy.data.scenes[0].CONFIG_Z.mat = '' 177 | bpy.data.scenes[0].CONFIG_Z.display = False 178 | 179 | 180 | def unregister(): 181 | if 'CONFIG_Z' in bpy.data.scenes[0] != None: 182 | del bpy.data.scenes[0]['CONFIG_Z'] 183 | del bpy.types.Scene.CONFIG_Z 184 | bpy.utils.unregister_class(UIElements) 185 | bpy.utils.unregister_class(ModalTimerOperator) 186 | bpy.utils.unregister_class(HelloWorldPanel) 187 | 188 | 189 | if __name__ == "__main__": 190 | register() 191 | #unregister() 192 | -------------------------------------------------------------------------------- /AutoExtrud.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | bl_info = { 4 | "name": "Mat_extrude", 5 | "author": "Alexander Nedovizin", 6 | "version": (0, 0, 8), 7 | "blender": (2, 6, 9), 8 | "location": "View3D > Toolbar", 9 | "description": "Materials extrude", 10 | "category": "Mesh" 11 | } 12 | 13 | # http://dl.dropboxusercontent.com/u/59609328/Blender-Rus/AutoExtrud.py 14 | 15 | import bpy, bmesh 16 | from mathutils import Vector 17 | 18 | list_z = [] 19 | mats_idx = [] 20 | list_f = [] 21 | maloe = 1e-5 22 | 23 | def getMats(context): 24 | global list_z, mats_idx, list_f, maloe 25 | 26 | obj = bpy.context.active_object 27 | me = obj.data 28 | 29 | bpy.ops.object.mode_set(mode='OBJECT') 30 | bpy.ops.object.mode_set(mode='EDIT') 31 | bpy.ops.mesh.select_mode(type='VERT') 32 | 33 | list_z = [v.co.z for v in me.vertices if v.select] 34 | list_z = list(set(list_z)) 35 | list_z.sort() 36 | 37 | bpy.ops.mesh.select_mode(type='FACE') 38 | list_f = [p.index for p in me.polygons if p.select] 39 | black_list = [] 40 | mats_idx = [] 41 | for z in list_z: 42 | for p in list_f: 43 | if p not in black_list: 44 | for v in me.polygons[p].vertices: 45 | if abs(me.vertices[v].co.z-z) Tools > For_export_AssettoCorsa", 9 | "description": "Помогаем создать файл настроек материалов для AssettoCorsa", 10 | "warning": "", 11 | "wiki_url": "", 12 | "tracker_url": "", 13 | "category": "Object"} 14 | 15 | 16 | from bpy_extras.io_utils import ExportHelper 17 | import json 18 | import bpy 19 | import os 20 | 21 | # http://dl.dropboxusercontent.com/u/59609328/blender-rus/for_export_AssettoCorsa.py 22 | 23 | def chekdir(filepath): 24 | if not os.path.exists(filepath): 25 | symb = os.path.sep 26 | filepath_ = filepath.rpartition(symb)[0] 27 | if filepath_=='': 28 | filepath_ = filepath 29 | filepath = filepath_ 30 | return filepath 31 | 32 | 33 | def saveMat2(materials={}, bl_addons_path_=''): 34 | bl_addons_path=chekdir(bl_addons_path_) 35 | Mat = {} 36 | full_line = '' 37 | if materials=={}: 38 | mats_names = [] 39 | else: 40 | mats_names = [m[0] for m in materials.items()] 41 | 42 | for matik in bpy.data.materials: 43 | name = matik.name 44 | mat_valid = name in mats_names 45 | 46 | alphaTested = matik.use_transparency 47 | sector_texture = False 48 | if bpy.data.materials[name].texture_slots[0]: 49 | AsphaltTex = bpy.data.materials[name].texture_slots[0].name 50 | sector_texture = True 51 | 52 | 53 | if alphaTested: 54 | boolt = 'true' 55 | else: 56 | boolt = 'false' 57 | 58 | telo = '{"shaderName" : "ksPerPixel",' \ 59 | +'"alphaBlendmode" : "Opaque",'\ 60 | +'"alphaTested" : '+boolt+',' \ 61 | +'"depthMode" : "DepthNormal",'\ 62 | +'"properties" : {'\ 63 | +'"ksDiffuse" : {'\ 64 | +'"valueA": '+str(0.4 if not mat_valid else materials[name]['properties']['ksDiffuse']['valueA']) \ 65 | +'},'\ 66 | +'"ksAmbient" : {'\ 67 | +'"valueA": '+str(0.4 if not mat_valid else materials[name]['properties']['ksAmbient']['valueA']) \ 68 | +'},'\ 69 | +'"ksSpecular" : {'\ 70 | +'"valueA": '+str(0.1 if not mat_valid else materials[name]['properties']['ksSpecular']['valueA']) \ 71 | +'},'\ 72 | +'"ksSpecularEXP" : {'\ 73 | +'"valueA": '+str(10 if not mat_valid else materials[name]['properties']['ksSpecularEXP']['valueA']) \ 74 | +'},'\ 75 | +'"ksEmissive" : {'\ 76 | +'"valueA": '+str(0 if not mat_valid else materials[name]['properties']['ksEmissive']['valueA']) \ 77 | +'},'\ 78 | +'"ksAlphaRef" : {'\ 79 | +'"valueA": '+str(0 if not mat_valid else materials[name]['properties']['ksAlphaRef']['valueA']) \ 80 | +'}}' 81 | 82 | if sector_texture: 83 | texture = ',"textures" : {"txDiffuse" : {'\ 84 | +'"slot" : 0,'\ 85 | +'"textureName" : "'+AsphaltTex+'"}}}' 86 | else: 87 | texture = '}' 88 | 89 | full_line = full_line+'"'+name+'":'+telo+texture+',' 90 | 91 | nodes_full = '' 92 | for obj in bpy.data.objects: 93 | objname = 'unusedTemplate' 94 | if len(obj.name)>5 and obj.name[:5]=='node_': 95 | objname = obj.name 96 | nodes_struc = '"'+objname+'":{'\ 97 | +'"lodIn": 0,'\ 98 | +'"lodOut": 1000,'\ 99 | +'"layer":0,'\ 100 | +'"castShadows": true,'\ 101 | +'"isVisible": true,'\ 102 | +'"isTransparent": false,'\ 103 | +'"isRenderable": true}' 104 | nodes_full = nodes_full + nodes_struc+',' 105 | 106 | 107 | res = json.loads('{'+full_line[:-1]+'}') 108 | Mat['materials']=res 109 | jnodes = json.loads('{'+nodes_full[:-1]+'}') 110 | Mat['nodes']=jnodes 111 | jdata = json.dumps(Mat, sort_keys=True, indent=4) 112 | svversion = os.path.normpath(os.path.join(bl_addons_path, 'settings.json')) 113 | print('\n\n****** for_export_AssettoCorsa.py '+str(bl_info['version'])+' *******') 114 | print(jdata) 115 | print(svversion) 116 | 117 | file = open(svversion, "w") 118 | file.write(jdata) 119 | file.close() 120 | return {'FINISHED'} 121 | 122 | 123 | def loadMat(bl_addons_path_): 124 | bl_addons_path=chekdir(bl_addons_path_) 125 | script_paths = os.path.normpath(os.path.dirname(__file__)) 126 | svversion = os.path.normpath(os.path.join(bl_addons_path, 'settings.json')) 127 | if not os.path.exists(svversion): 128 | return {} 129 | 130 | file = open(svversion, 'r') 131 | Mats_=file.read() 132 | file.close() 133 | 134 | Mats = json.loads(Mats_) 135 | materials = Mats['materials'] 136 | 137 | jdata = json.dumps(Mats, sort_keys=True, indent=4) 138 | return materials 139 | 140 | 141 | class Faec_op(bpy.types.Operator): 142 | 143 | bl_idname = "object.faec" 144 | bl_label = "For export AssettoCorsa" 145 | bl_options = {'REGISTER', 'UNDO', 'PRESET'} 146 | 147 | def execute(self, context): 148 | bpy.ops.export_test.some_data('INVOKE_DEFAULT') 149 | return {'FINISHED'} 150 | 151 | 152 | class Faec_panel(bpy.types.Panel): 153 | """Creates a Panel in the Object properties window""" 154 | bl_label = "For_export_AssettoCorsa" 155 | bl_idname = "OBJECT_PT_faec" 156 | bl_space_type = 'VIEW_3D' 157 | bl_region_type = 'TOOLS' 158 | bl_options = {'DEFAULT_CLOSED'} 159 | 160 | def draw(self, context): 161 | layout = self.layout 162 | col = layout.column(align=True) 163 | col.operator("object.faec", text = 'Выгрузить настройки!') 164 | 165 | 166 | class ExportSomeData(bpy.types.Operator, ExportHelper): 167 | """This appears in the tooltip of the operator and in the generated docs""" 168 | bl_idname = "export_test.some_data" # important since its how bpy.ops.import_test.some_data is constructed 169 | bl_label = "Export Some Data" 170 | 171 | # ExportHelper mixin class uses this 172 | filename_ext = "" 173 | 174 | def execute(self, context): 175 | return saveMat2(loadMat(self.filepath), self.filepath) 176 | 177 | 178 | 179 | 180 | classes = [Faec_op, Faec_panel, ExportSomeData] 181 | 182 | # registering and menu integration 183 | def register(): 184 | for c in classes: 185 | bpy.utils.register_class(c) 186 | 187 | # unregistering and removing menus 188 | def unregister(): 189 | for c in reversed(classes): 190 | bpy.utils.unregister_class(c) 191 | 192 | if __name__ == "__main__": 193 | register() 194 | 195 | -------------------------------------------------------------------------------- /PolyCamsRender4.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import bpy, math 4 | import os 5 | import time 6 | from bpy_extras.io_utils import ExportHelper 7 | from bpy.props import StringProperty, BoolProperty, EnumProperty 8 | from bpy.types import Operator 9 | 10 | bl_info = { 11 | "name": "PolyCams Render", 12 | "author": "Alexander Nedovizin", 13 | "version": (0, 4, 9), 14 | "blender": (2, 6, 9), 15 | "category": "Render" 16 | } 17 | 18 | # http://dl.dropboxusercontent.com/u/59609328/Blender-Rus/PolyCamsRender4.py 19 | 20 | class ExportSomeData(Operator, ExportHelper): 21 | """This appears in the tooltip of the operator and in the generated docs""" 22 | bl_idname = "export_test.some_data" # important since its how bpy.ops.import_test.some_data is constructed 23 | bl_label = "Export Some Data" 24 | 25 | # ExportHelper mixin class uses this 26 | filename_ext = "" 27 | 28 | def execute(self, context): 29 | return render_me(self.filepath) 30 | 31 | 32 | def render_me(filepath): 33 | sceneName = bpy.context.scene.name 34 | glob_res = [bpy.context.scene.render.resolution_x, bpy.context.scene.render.resolution_y] 35 | 36 | bpy.data.scenes[sceneName].render.filepath = filepath 37 | if not os.path.exists(filepath): 38 | os.mkdir(filepath) 39 | outputfile_s = os.path.join(filepath, 'stat.txt') 40 | with open(outputfile_s, 'w') as w_file: 41 | w_file.write('Batch stats:\n'+'_________________________________\n') 42 | 43 | 44 | camsi = [] 45 | progress = 0 46 | sline = '' 47 | backet_x = bpy.context.scene.render.tile_x 48 | backet_y = bpy.context.scene.render.tile_y 49 | sq_backet = backet_x*backet_y 50 | rp = bpy.context.scene.render.resolution_percentage 51 | for cam in bpy.data.objects: 52 | if ( cam.type =='CAMERA' and not cam.hide_render): 53 | flag = False 54 | res = cam.data.name.split('(') 55 | res_x = glob_res[0] 56 | res_y = glob_res[1] 57 | if len(res)==2: 58 | res = res[1].split(')') 59 | if len(res)==2: 60 | res = res[0].split('+') 61 | if len(res)==2: 62 | res_x = int(res[0]) 63 | res_y = int(res[1]) 64 | flag = True 65 | 66 | camsi.append((res_x,res_y)) 67 | p_tmp = res_x * res_y 68 | p_tmp_scale = round(p_tmp*rp/100) 69 | progress += p_tmp 70 | if flag: 71 | sline = sline + cam.name +' | '+str(res_x)+'x'+str(res_y)+' | '+ \ 72 | str(round(res_x*rp/100))+'x'+str(round(res_y*rp/100))+' | '+ \ 73 | str(round(p_tmp_scale/sq_backet))+'\n' 74 | else: 75 | sline = sline + cam.name +' | default | '+ \ 76 | str(round(res_x*rp/100))+'x'+str(round(res_y*rp/100))+' | '+ \ 77 | str(round(p_tmp_scale/sq_backet))+'\n' 78 | 79 | 80 | with open(outputfile_s, 'a') as w_file: 81 | w_file.write('Total resolution = '+str(round(math.sqrt(progress)))+'x'+str(round(math.sqrt(progress)))+ \ 82 | ' ('+str(round(math.sqrt(progress)*rp/100))+'x'+str(round(math.sqrt(progress)*rp/100))+')'+'\n') 83 | w_file.write('Default Resolution = '+str(glob_res[0])+'x'+str(glob_res[1])+' ('+str(rp)+'%)'+'\n') 84 | w_file.write('Tiles = '+str(backet_x)+'x'+ str(backet_y)+'\n') 85 | w_file.write('Total tiles = '+str(round(progress*rp/(sq_backet*100)))+'\n\n') 86 | w_file.write('Cameras:\n'+'Name | resolution | scaled ('+str(rp)+'%) | tiles\n'+ \ 87 | '________________________________________\n') 88 | w_file.write(sline) 89 | 90 | 91 | outputfile = os.path.join(filepath, 'log.txt') 92 | with open(outputfile, 'w') as w_file: 93 | w_file.write('Cameras:\n'+'Name | resolution | scaled ('+str(rp)+'%) | progress % | remaining time | elapsed time\n'+ \ 94 | '_____________________________________________________________________________\n') 95 | 96 | p_tmp = 0 97 | time_start = time.time() 98 | i = 0 99 | for cam in bpy.data.objects: 100 | if ( cam.type =='CAMERA' and not cam.hide_render): 101 | bpy.data.scenes[sceneName].camera = cam 102 | bpy.context.scene.render.resolution_x = camsi[i][0] 103 | bpy.context.scene.render.resolution_y = camsi[i][1] 104 | bpy.data.scenes[sceneName].render.filepath = filepath+'\\'+cam.name 105 | bpy.ops.render.render(animation=False, write_still = True) 106 | p_tmp += camsi[i][0]*camsi[i][1] 107 | proc = max(round(p_tmp*100/progress),1) 108 | r_time = time.time() - time_start 109 | time_tmp = r_time*(100-proc)/proc 110 | time_tmp = round(time_tmp) 111 | 112 | s_rt = time.strftime('%H:%M:%S',time.gmtime(r_time)) 113 | s_lt = time.strftime('%H:%M:%S',time.gmtime(time_tmp)) 114 | with open(outputfile, 'a') as w_file: 115 | w_file.write(cam.name +' | '+str(camsi[i][0])+'x'+str(camsi[i][1])+' | '+ \ 116 | str(round(camsi[i][0]*rp/100))+'x'+str(round(camsi[i][1]*rp/100))+' | '+ \ 117 | str(proc)+' | '+s_lt+' | '+s_rt+'\n') 118 | i += 1 119 | 120 | bpy.context.scene.render.resolution_x = glob_res[0] 121 | bpy.context.scene.render.resolution_y = glob_res[1] 122 | #print('Done!') 123 | #print(bpy.data.scenes[sceneName].render.filepath) 124 | return {'FINISHED'} 125 | 126 | 127 | class RenderMe(bpy.types.Operator): 128 | """Cams render""" 129 | bl_idname = "scene.render_me" 130 | bl_label = "Render Me" 131 | bl_options = {'REGISTER', 'UNDO'} 132 | 133 | def execute(self, context): 134 | bpy.ops.export_test.some_data('INVOKE_DEFAULT') 135 | return {'FINISHED'} 136 | 137 | 138 | def menu_func(self, context): 139 | self.layout.operator(RenderMe.bl_idname) 140 | def menu_func_export(self, context): 141 | self.layout.operator(ExportSomeData.bl_idname, text="Cams Render!") 142 | 143 | 144 | addon_keymaps = [] 145 | 146 | def register(): 147 | bpy.utils.register_class(RenderMe) 148 | bpy.types.VIEW3D_MT_object.append(menu_func) 149 | 150 | bpy.utils.register_class(ExportSomeData) 151 | bpy.types.INFO_MT_file_export.append(menu_func_export) 152 | 153 | # handle the keymap 154 | wm = bpy.context.window_manager 155 | km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY') 156 | kmi = km.keymap_items.new(RenderMe.bl_idname, 'F12', 'PRESS', alt=True, shift=False) 157 | addon_keymaps.append((km, kmi)) 158 | 159 | def unregister(): 160 | bpy.utils.unregister_class(RenderMe) 161 | bpy.types.VIEW3D_MT_object.remove(menu_func) 162 | 163 | bpy.utils.unregister_class(ExportSomeData) 164 | bpy.types.INFO_MT_file_export.remove(menu_func_export) 165 | 166 | # handle the keymap 167 | for km, kmi in addon_keymaps: 168 | km.keymap_items.remove(kmi) 169 | addon_keymaps.clear() 170 | 171 | 172 | if __name__ == "__main__": 173 | register() -------------------------------------------------------------------------------- /mesh_gf_manager_13.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | ## DTT 1.2.1.1 (c)2013 FSL - FreeSoftLand 3 | ## Title: Бета версия GridFill Manager 1_3 4 | ## 5 | ## Date : 26.07.2013 6 | ## By : Cfyzzz 7 | ############################################################################### 8 | 9 | import bpy, bmesh, copy 10 | from bpy.props import IntProperty, StringProperty 11 | 12 | select_verts = [] 13 | active_vert = -1 14 | 15 | 16 | bl_info = { 17 | "name": "GridFill Manager", 18 | "author": "Alexander Nedovizin", 19 | "version": (0, 1, 3), 20 | "blender": (2, 6, 8), 21 | "description": "GridFill Manager", 22 | "category": "Mesh" 23 | } 24 | 25 | 26 | def check_context(obj): 27 | global select_verts, active_vert 28 | res = True 29 | bpy.ops.object.mode_set(mode='OBJECT') 30 | bpy.ops.object.mode_set(mode='EDIT') 31 | 32 | sv = find_index_of_selected_vertex(obj) 33 | for v in sv: 34 | if not(v in select_verts): 35 | res = False 36 | 37 | return res 38 | 39 | 40 | def update_shape(self,context): 41 | global select_verts, active_vert 42 | 43 | ch_c = check_context(context.object) 44 | 45 | if not ch_c: 46 | main(context, context.object.myRad, mode = True) 47 | else: 48 | 49 | selecting_verts(context.object.data,select_verts) 50 | main(context, context.object.myRad, mode = True) 51 | 52 | 53 | 54 | bpy.types.Object.myRad = IntProperty( 55 | name="Radius", 56 | min = 0, 57 | default = 1, 58 | update = update_shape) 59 | 60 | bpy.types.Object.mnogo_v= IntProperty( 61 | name="Vertices", 62 | default = -1) 63 | 64 | 65 | def bm_vert_active_get(ob): 66 | bm = bmesh.from_edit_mesh(ob.data) 67 | for elem in reversed(bm.select_history): 68 | if isinstance(elem, bmesh.types.BMVert): 69 | return elem.index 70 | return -2 71 | 72 | 73 | def find_index_of_selected_vertex(obj): 74 | selected_verts = [i.index for i in obj.data.vertices if i.select] 75 | verts_selected = len(selected_verts) 76 | if verts_selected <1: 77 | return None 78 | else: 79 | return selected_verts 80 | 81 | 82 | def find_connected_verts(me, found_index, not_list): 83 | 84 | edges = me.edges 85 | connecting_edges = [i for i in edges if found_index in i.vertices[:]] 86 | if len(connecting_edges) == 0: 87 | return [] 88 | else: 89 | connected_verts = [] 90 | for edge in connecting_edges: 91 | cvert = set(edge.vertices[:]) 92 | cvert.remove(found_index) 93 | vert = cvert.pop() 94 | if not (vert in not_list) and me.vertices[vert].select: 95 | connected_verts.append(vert) 96 | 97 | return connected_verts 98 | 99 | 100 | def get_loop(me, active_v, v_set, not_list=[], step=0): 101 | vlist = [active_v] 102 | ln = len(v_set) 103 | not_list.append(active_v) 104 | 105 | step +=1 106 | list_v_1 = find_connected_verts(me, active_v, not_list) 107 | 108 | if step==ln: 109 | return vlist 110 | 111 | if len(list_v_1)>0: 112 | list_v_2 = get_loop(me, list_v_1[0], v_set, not_list, step) 113 | vlist += list_v_2 114 | 115 | return vlist 116 | 117 | 118 | def get_opposite(me,vert_index, v_set): 119 | loop = [] 120 | loop = get_loop(me, vert_index, v_set, []) 121 | 122 | ps = len(loop)//2 123 | ff = loop.index(vert_index) 124 | 125 | if ff>=ps: 126 | df = ff-ps 127 | else: 128 | df = ff+ps 129 | 130 | return loop[df] 131 | 132 | 133 | 134 | def find_all_connected_verts(R, me, active_v, not_list=[], step=0): 135 | vlist = [active_v] 136 | not_list.append(active_v) 137 | step+=1 138 | list_v_1 = find_connected_verts(me, active_v, not_list) 139 | 140 | if step==R+2: 141 | return vlist 142 | 143 | for v in list_v_1: 144 | list_v_2 = find_all_connected_verts(R, me, v, not_list, step) 145 | vlist += list_v_2 146 | 147 | return vlist 148 | 149 | 150 | def selecting_verts(me, mas): 151 | bpy.ops.object.mode_set(mode='OBJECT') 152 | 153 | for idx in mas: 154 | me.vertices[idx].select = True 155 | 156 | bpy.ops.object.mode_set(mode='EDIT') 157 | 158 | 159 | def deselecting_verts(me, mas): 160 | bpy.ops.object.mode_set(mode='OBJECT') 161 | 162 | for idx in mas: 163 | me.vertices[idx].select = False 164 | 165 | bpy.ops.object.mode_set(mode='EDIT') 166 | 167 | 168 | 169 | def cls_mnogo(obj): 170 | obj.mnogo_v = -1 171 | bpy.ops.ed.undo() 172 | return 173 | 174 | 175 | 176 | def main(context, Rad=1, Dist=0, mode=False): 177 | global select_verts, active_vert, mnogo_v 178 | 179 | bpy.ops.object.mode_set(mode='OBJECT') 180 | bpy.ops.object.mode_set(mode='EDIT') 181 | 182 | ob = bpy.context.object 183 | 184 | if active_vert >= 0 and ob.mnogo_v>0 and mode: 185 | cls_mnogo(ob) 186 | 187 | ch_c = check_context(ob) 188 | 189 | if mode: 190 | av = -1 191 | if ch_c: 192 | av = active_vert 193 | else: 194 | av = bm_vert_active_get(ob) 195 | if ch_c: 196 | active_vert = -1 197 | 198 | if (active_vert < 0 or active_vert != av) and not mode: 199 | active_vert = bm_vert_active_get(ob) 200 | select_verts=[] 201 | 202 | if active_vert==-2 and not mode: 203 | print_error('Active vert is not define') 204 | return{'Error: 003'} 205 | 206 | if select_verts==[]: 207 | select_verts = find_index_of_selected_vertex(ob) 208 | 209 | 210 | sv_len = len(select_verts) 211 | 212 | if sv_len<8: 213 | print_error('Error: need for equ or more then 8 verts') 214 | return{'Error: 001'} 215 | 216 | if (sv_len//8)0 and not mode: 220 | print_error('Error: must be an even number of vertices') 221 | return{'Error: 002'} 222 | 223 | mesh = ob.data 224 | opposit_vert = get_opposite(mesh,active_vert, select_verts) 225 | 226 | nl = [] 227 | new_sel_v_1 = find_all_connected_verts(Rad, mesh, active_vert, nl) 228 | new_sel_v_2 = find_all_connected_verts(Rad, mesh, opposit_vert, nl) 229 | 230 | bpy.ops.mesh.select_all(action='DESELECT') 231 | bpy.ops.object.mode_set(mode='OBJECT') 232 | 233 | for idx in nl: 234 | mesh.vertices[idx].select = True 235 | 236 | bpy.ops.object.mode_set(mode='EDIT') 237 | bpy.ops.mesh.fill_grid() 238 | ob.mnogo_v = len(mesh.vertices) 239 | 240 | return {'FINISHED'} 241 | 242 | 243 | class MessageOperator(bpy.types.Operator): 244 | bl_idname = "error.message" 245 | bl_label = "Message" 246 | type = StringProperty() 247 | message = StringProperty() 248 | 249 | def execute(self, context): 250 | self.report({'INFO'}, self.message) 251 | print(self.message) 252 | return {'FINISHED'} 253 | 254 | def invoke(self, context, event): 255 | wm = context.window_manager 256 | return wm.invoke_popup(self, width=400, height=200) 257 | 258 | def draw(self, context): 259 | self.layout.label(self.message, icon='BLENDER') 260 | 261 | 262 | def print_error(message): 263 | bpy.ops.error.message('INVOKE_DEFAULT', 264 | type = "Message", 265 | message = message) 266 | 267 | 268 | class GFManagerOperator(bpy.types.Operator): 269 | """Tooltip""" 270 | bl_idname = "mesh.gfmanager_operator" 271 | bl_label = "GridFill Manager Operator" 272 | bl_options = {'REGISTER', 'UNDO'} 273 | rad = bpy.props.IntProperty() 274 | 275 | @classmethod 276 | def poll(cls, context): 277 | return context.active_object is not None 278 | 279 | def execute(self, context): 280 | main(context, context.object.myRad) 281 | return {'FINISHED'} 282 | 283 | 284 | 285 | 286 | class GFManagerPanel(bpy.types.Panel): 287 | """Creates a Panel in the Object properties window""" 288 | bl_label = "Grid Fill Manager" 289 | bl_idname = "OBJECT_PT_GFManagerPanel" 290 | bl_space_type = 'VIEW_3D' 291 | bl_region_type = 'UI' 292 | bl_context = "object" 293 | 294 | def draw(self, context): 295 | layout = self.layout 296 | 297 | obj = context.object 298 | 299 | row = layout.row() 300 | row.label(text="Active object is: " + obj.name) 301 | row = layout.row() 302 | row.prop(obj, "name") 303 | layout.prop(obj, 'myRad') 304 | 305 | row = layout.row() 306 | row.operator("mesh.gfmanager_operator", text='Ok') 307 | 308 | 309 | def register(): 310 | bpy.utils.register_class(GFManagerPanel) 311 | bpy.utils.register_class(GFManagerOperator) 312 | bpy.utils.register_class(MessageOperator) 313 | 314 | 315 | def unregister(): 316 | bpy.utils.unregister_class(GFManagerPanel) 317 | bpy.utils.unregister_class(GFManagerOperator) 318 | bpy.utils.unregister_class(MessageOperator) 319 | 320 | 321 | if __name__ == "__main__": 322 | register() 323 | 324 | -------------------------------------------------------------------------------- /F2_164.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': "F2", 23 | 'author': "Bart Crouch, Alexander Nedovizin", 24 | 'version': (1, 6, 4), 25 | 'blender': (2, 68, 0), 26 | 'location': "Editmode > F", 27 | 'warning': "", 28 | 'description': "Extends the 'Make Edge/Face' functionality", 29 | 'wiki_url': "http://wiki.blender.org/index.php/Extensions:2.6/Py/"\ 30 | "Scripts/Modeling/F2", 31 | 'tracker_url': "http://projects.blender.org/tracker/index.php?"\ 32 | "func=detail&aid=33979", 33 | 'category': 'Mesh'} 34 | 35 | 36 | import bmesh 37 | import bpy 38 | import itertools 39 | import mathutils 40 | from bpy_extras import view3d_utils 41 | from bpy.props import StringProperty, BoolProperty 42 | from bpy.types import AddonPreferences 43 | 44 | class F2AddonPreferences(AddonPreferences): 45 | bl_idname = __name__ 46 | 47 | boolean = BoolProperty( 48 | name="Auto Grab", 49 | default=False, 50 | ) 51 | 52 | def draw(self, context): 53 | layout = self.layout 54 | layout.label(text="Settings") 55 | layout.prop(self, "boolean") 56 | 57 | # create a face from a single selected edge 58 | def quad_from_edge(bm, edge_sel, context, event): 59 | ob = context.active_object 60 | region = context.region 61 | region_3d = context.space_data.region_3d 62 | 63 | # find linked edges that are open (<2 faces connected) and not part of 64 | # the face the selected edge belongs to 65 | all_edges = [[edge for edge in edge_sel.verts[i].link_edges if \ 66 | len(edge.link_faces) < 2 and edge != edge_sel and \ 67 | sum([face in edge_sel.link_faces for face in edge.link_faces]) == 0] \ 68 | for i in range(2)] 69 | if not all_edges[0] or not all_edges[1]: 70 | return 71 | 72 | # determine which edges to use, based on mouse cursor position 73 | mouse_pos = mathutils.Vector([event.mouse_region_x, event.mouse_region_y]) 74 | optimal_edges = [] 75 | for edges in all_edges: 76 | min_dist = False 77 | for edge in edges: 78 | vert = [vert for vert in edge.verts if not vert.select][0] 79 | world_pos = ob.matrix_world * vert.co.copy() 80 | screen_pos = view3d_utils.location_3d_to_region_2d(region, 81 | region_3d, world_pos) 82 | dist = (mouse_pos - screen_pos).length 83 | if not min_dist or dist < min_dist[0]: 84 | min_dist = (dist, edge, vert) 85 | optimal_edges.append(min_dist) 86 | 87 | # determine the vertices, which make up the quad 88 | v1 = edge_sel.verts[0] 89 | v2 = edge_sel.verts[1] 90 | edge_1 = optimal_edges[0][1] 91 | edge_2 = optimal_edges[1][1] 92 | v3 = optimal_edges[0][2] 93 | v4 = optimal_edges[1][2] 94 | 95 | # normal detection 96 | flip_align = True 97 | normal_edge = edge_1 98 | if not normal_edge.link_faces: 99 | normal_edge = edge_2 100 | if not normal_edge.link_faces: 101 | normal_edge = edge_sel 102 | if not normal_edge.link_faces: 103 | # no connected faces, so no need to flip the face normal 104 | flip_align = False 105 | if flip_align: # there is a face to which the normal can be aligned 106 | ref_verts = [v for v in normal_edge.link_faces[0].verts] 107 | if v3 in ref_verts: 108 | va_1 = v3 109 | va_2 = v1 110 | elif normal_edge == edge_sel: 111 | va_1 = v1 112 | va_2 = v2 113 | else: 114 | va_1 = v2 115 | va_2 = v4 116 | if (va_1 == ref_verts[0] and va_2 == ref_verts[-1]) or \ 117 | (va_2 == ref_verts[0] and va_1 == ref_verts[-1]): 118 | # reference verts are at start and end of the list -> shift list 119 | ref_verts = ref_verts[1:] + [ref_verts[0]] 120 | if ref_verts.index(va_1) > ref_verts.index(va_2): 121 | # connected face has same normal direction, so don't flip 122 | flip_align = False 123 | 124 | # material index detection 125 | ref_faces = edge_sel.link_faces 126 | if not ref_faces: 127 | ref_faces = edge_sel.verts[0].link_faces 128 | if not ref_faces: 129 | ref_faces = edge_sel.verts[1].link_faces 130 | if not ref_faces: 131 | mat_index = False 132 | smooth = False 133 | else: 134 | mat_index = ref_faces[0].material_index 135 | smooth = ref_faces[0].smooth 136 | 137 | # create quad 138 | try: 139 | verts = [v3, v1, v2, v4] 140 | if flip_align: 141 | verts.reverse() 142 | face = bm.faces.new(verts) 143 | if mat_index: 144 | face.material_index = mat_index 145 | face.smooth = smooth 146 | except: 147 | # face already exists 148 | return 149 | 150 | # change selection 151 | edge_sel.select = False 152 | for vert in edge_sel.verts: 153 | vert.select = False 154 | for edge in face.edges: 155 | if edge.index < 0: 156 | edge.select = True 157 | v3.select = True 158 | v4.select = True 159 | 160 | # toggle mode, to force correct drawing 161 | bpy.ops.object.mode_set(mode='OBJECT') 162 | bpy.ops.object.mode_set(mode='EDIT') 163 | 164 | 165 | def decorator_grab(func): 166 | #decorate function invoke of class MeshF2 167 | def bm_vert_active_get(ob): 168 | bm = bmesh.from_edit_mesh(ob.data) 169 | for elem in reversed(bm.select_history): 170 | if isinstance(elem, bmesh.types.BMVert): 171 | return elem.index 172 | return -1 173 | 174 | 175 | def wrapper(self, context, event): 176 | obj = bpy.context.active_object 177 | bm = bmesh.from_edit_mesh(obj.data) 178 | sel = [v for v in bm.verts if v.select] 179 | if len(sel) == 1 and bm_vert_active_get(obj)==-1: 180 | return {'CANCELLED'} 181 | 182 | 183 | res = func(self, context, event) 184 | bpy.ops.object.mode_set(mode='OBJECT') 185 | bpy.ops.object.mode_set(mode='EDIT') 186 | 187 | user_preferences = context.user_preferences 188 | addon_prefs = user_preferences.addons[__name__].preferences 189 | 190 | obj = bpy.context.active_object 191 | selected_verts = [i.index for i in obj.data.vertices if i.select] 192 | if len(selected_verts) == 1 and addon_prefs.boolean: 193 | bpy.ops.transform.translate('INVOKE_DEFAULT') 194 | 195 | return res 196 | return wrapper 197 | 198 | def quad_from_vertex(bm, vert_sel, context, event): 199 | ob = context.active_object 200 | region = context.region 201 | region_3d = context.space_data.region_3d 202 | 203 | # find linked edges that are open (<2 faces connected) 204 | edges = [edge for edge in vert_sel.link_edges if len(edge.link_faces) < 2] 205 | if len(edges) < 2: 206 | return 207 | 208 | # determine which edges to use, based on mouse cursor position 209 | min_dist = False 210 | mouse_pos = mathutils.Vector([event.mouse_region_x, event.mouse_region_y]) 211 | for a, b in itertools.combinations(edges, 2): 212 | other_verts = [vert for edge in [a, b] for vert in edge.verts \ 213 | if not vert.select] 214 | mid_other = (other_verts[0].co.copy() + other_verts[1].co.copy()) \ 215 | / 2 216 | new_pos = 2 * (mid_other - vert_sel.co.copy()) + vert_sel.co.copy() 217 | world_pos = ob.matrix_world * new_pos 218 | screen_pos = view3d_utils.location_3d_to_region_2d(region, region_3d, 219 | world_pos) 220 | dist = (mouse_pos - screen_pos).length 221 | if not min_dist or dist < min_dist[0]: 222 | min_dist = (dist, (a, b), other_verts, new_pos) 223 | 224 | # create vertex at location mirrored in the line, connecting the open edges 225 | edges = min_dist[1] 226 | other_verts = min_dist[2] 227 | new_pos = min_dist[3] 228 | vert_new = bm.verts.new(new_pos) 229 | 230 | # normal detection 231 | flip_align = True 232 | normal_edge = edges[0] 233 | if not normal_edge.link_faces: 234 | normal_edge = edges[1] 235 | if not normal_edge.link_faces: 236 | # no connected faces, so no need to flip the face normal 237 | flip_align = False 238 | if flip_align: # there is a face to which the normal can be aligned 239 | ref_verts = [v for v in normal_edge.link_faces[0].verts] 240 | if other_verts[0] in ref_verts: 241 | va_1 = other_verts[0] 242 | va_2 = vert_sel 243 | else: 244 | va_1 = vert_sel 245 | va_2 = other_verts[1] 246 | if (va_1 == ref_verts[0] and va_2 == ref_verts[-1]) or \ 247 | (va_2 == ref_verts[0] and va_1 == ref_verts[-1]): 248 | # reference verts are at start and end of the list -> shift list 249 | ref_verts = ref_verts[1:] + [ref_verts[0]] 250 | if ref_verts.index(va_1) > ref_verts.index(va_2): 251 | # connected face has same normal direction, so don't flip 252 | flip_align = False 253 | 254 | # material index detection 255 | ref_faces = vert_sel.link_faces 256 | if not ref_faces: 257 | mat_index = False 258 | smooth = False 259 | else: 260 | mat_index = ref_faces[0].material_index 261 | smooth = ref_faces[0].smooth 262 | 263 | # create face between all 4 vertices involved 264 | verts = [other_verts[0], vert_sel, other_verts[1], vert_new] 265 | if flip_align: 266 | verts.reverse() 267 | face = bm.faces.new(verts) 268 | if mat_index: 269 | face.material_index = mat_index 270 | face.smooth = smooth 271 | 272 | # change selection 273 | vert_new.select = True 274 | vert_sel.select = False 275 | 276 | # toggle mode, to force correct drawing 277 | bpy.ops.object.mode_set(mode='OBJECT') 278 | bpy.ops.object.mode_set(mode='EDIT') 279 | 280 | 281 | class MeshF2(bpy.types.Operator): 282 | """Tooltip""" 283 | bl_idname = "mesh.f2" 284 | bl_label = "Make Edge/Face" 285 | bl_description = "Extends the 'Make Edge/Face' functionality" 286 | bl_options = {'REGISTER', 'UNDO'} 287 | 288 | 289 | 290 | @classmethod 291 | def poll(cls, context): 292 | # check we are in mesh editmode 293 | ob = context.active_object 294 | return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH') 295 | 296 | @decorator_grab #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 297 | def invoke(self, context, event): 298 | bm = bmesh.from_edit_mesh(context.active_object.data) 299 | sel = [v for v in bm.verts if v.select] 300 | if len(sel) > 2: 301 | # original 'Make Edge/Face' behaviour 302 | try: 303 | bpy.ops.mesh.edge_face_add('INVOKE_DEFAULT') 304 | except: 305 | return {'CANCELLED'} 306 | elif len(sel) == 1: 307 | # single vertex selected -> mirror vertex and create new face 308 | quad_from_vertex(bm, sel[0], context, event) 309 | elif len(sel) == 2: 310 | edges_sel = [ed for ed in bm.edges if ed.select] 311 | if len(edges_sel) != 1: 312 | # 2 vertices selected, but not on the same edge 313 | bpy.ops.mesh.edge_face_add() 314 | else: 315 | # single edge selected -> new face from linked open edges 316 | quad_from_edge(bm, edges_sel[0], context, event) 317 | 318 | return {'FINISHED'} 319 | 320 | 321 | # registration 322 | classes = [MeshF2, F2AddonPreferences] 323 | addon_keymaps = [] 324 | 325 | 326 | def register(): 327 | # add operator 328 | for c in classes: 329 | bpy.utils.register_class(c) 330 | 331 | # add keymap entry 332 | km = bpy.context.window_manager.keyconfigs.addon.keymaps.new(\ 333 | name='Mesh', space_type='EMPTY') 334 | kmi = km.keymap_items.new("mesh.f2", 'F', 'PRESS') 335 | addon_keymaps.append((km, kmi)) 336 | 337 | 338 | def unregister(): 339 | # remove keymap entry 340 | for km, kmi in addon_keymaps: 341 | km.keymap_items.remove(kmi) 342 | #bpy.context.window_manager.keyconfigs.addon.keymaps.remove(km) 343 | addon_keymaps.clear() 344 | 345 | # remove operator 346 | for c in reversed(classes): 347 | bpy.utils.unregister_class(c) 348 | 349 | if __name__ == "__main__": 350 | register() 351 | -------------------------------------------------------------------------------- /RetopoQuads.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | bl_info = { 4 | "name": "RetopoQuads", 5 | "author": "Nedovizin Alexander", 6 | "version": (0, 0, 11), 7 | "blender": (2, 6, 9), 8 | "location": "Console > QQUAD", 9 | "description": "Create quads retopology", 10 | "category": "Mesh"} 11 | 12 | import bpy 13 | from mathutils import Vector 14 | 15 | # http://dl.dropboxusercontent.com/u/59609328/Blender-Rus/RetopoQuads.py 16 | 17 | def find_connected_verts(me, found_index, not_list): 18 | edges = me.edges 19 | connecting_edges = [i for i in edges if found_index in i.vertices[:] and \ 20 | me.vertices[i.vertices[0]].select and me.vertices[i.vertices[1]].select] 21 | if len(connecting_edges) == 0: 22 | return [] 23 | else: 24 | connected_verts = [] 25 | for edge in connecting_edges: 26 | fe = 0 27 | cvert = edge.vertices[:] 28 | for f in me.polygons: 29 | vf = [] 30 | for v in f.vertices: 31 | vf.append(v) 32 | 33 | if cvert[0] in vf and cvert[1] in vf: fe += 1 34 | if fe>1: continue 35 | 36 | cvert = set(cvert) 37 | cvert.remove(found_index) 38 | vert = cvert.pop() 39 | if not (vert in not_list) and me.vertices[vert].select and \ 40 | True: 41 | connected_verts.append(vert) 42 | 43 | return connected_verts 44 | 45 | 46 | def find_all_connected_verts(me, active_v, not_list=[]): 47 | vlist = [active_v] 48 | not_list.append(active_v) 49 | list_v_1 = find_connected_verts(me, active_v, not_list) 50 | 51 | for v in list_v_1: 52 | list_v_2 = find_all_connected_verts(me, v, not_list) 53 | vlist += list_v_2 54 | 55 | return vlist 56 | 57 | 58 | def getLoops(me, verts): 59 | vsa = verts.copy() 60 | result = [] 61 | while len(vsa)>0: 62 | loop = find_all_connected_verts(me,vsa[0],not_list=[])[:-1] 63 | for v in loop: 64 | vsa.remove(v) 65 | result.append(loop) 66 | return result 67 | 68 | 69 | def createMeshFromData(name, origin, verts, faces): 70 | # Создание меша и объекта 71 | me = bpy.data.meshes.new(name+'Mesh') 72 | ob = bpy.data.objects.new(name, me) 73 | ob.location = origin 74 | ob.show_name = True 75 | 76 | # Привязка объекта к сцене, он становится активным 77 | scn = bpy.context.scene 78 | scn.objects.link(ob) 79 | scn.objects.active = ob 80 | ob.select = True 81 | 82 | # Создание меша из полученных verts (вершин), faces (граней). 83 | me.from_pydata(verts, [], faces) 84 | # Обновление меша с новыми данными 85 | me.update() 86 | return ob 87 | 88 | 89 | def vecscorrect(vec, vec0): 90 | norm = vec.normalized() 91 | 92 | mat_rot_norm = vec0.rotation_difference(norm).to_matrix().to_4x4() 93 | negative_mat = norm.rotation_difference(vec0).to_matrix().to_4x4() 94 | 95 | return mat_rot_norm, negative_mat 96 | 97 | 98 | def select_planar(me, indx_face, origins = Vector((0,0,0))): 99 | bpy.ops.object.mode_set(mode='EDIT') 100 | bpy.ops.mesh.select_mode(type='FACE') 101 | bpy.ops.mesh.select_all(action='DESELECT') 102 | bpy.ops.object.editmode_toggle() 103 | me.polygons[indx_face].select = True 104 | bpy.ops.object.editmode_toggle() 105 | 106 | # ********** start select magic ********* 107 | face = 0 108 | for i,f in enumerate(me.polygons): 109 | if f.select: 110 | face = i 111 | break 112 | 113 | bpy.ops.mesh.select_all(action='DESELECT') 114 | normal = me.polygons[face].normal.copy() 115 | 116 | bpy.ops.object.mode_set(mode='OBJECT') 117 | vers_s = [] 118 | pols = [] 119 | for i,f in enumerate(me.polygons): 120 | mul = normal * f.normal 121 | if abs(mul)>1-1e-6: 122 | vers_s.extend([v for v in me.polygons[i].vertices]) 123 | pols.append(f) 124 | 125 | flag = True 126 | faces = [face] 127 | eds = [v for v in me.polygons[face].edge_keys] 128 | while flag: 129 | flag = False 130 | eds_ = [] 131 | for pp in pols: 132 | if pp.index in faces: 133 | continue 134 | 135 | eds_ = [v for v in pp.edge_keys if v in eds or \ 136 | (v[1],v[0]) in eds] 137 | 138 | if eds_!=[]: 139 | flag = True 140 | faces.append(pp.index) 141 | eds.extend([v for v in pp.edge_keys]) 142 | 143 | for f in faces: 144 | me.polygons[f].select = True 145 | # *************** end select magic ********** 146 | 147 | v_dic = {} 148 | f_list = [] 149 | v_list = [] 150 | v_cou = 0 151 | 152 | bpy.ops.object.mode_set(mode='OBJECT') 153 | pols = [p for p in me.polygons if p.select] 154 | for i,f in enumerate(pols): 155 | v_ = [] 156 | for iv, v in enumerate(f.vertices[:]): 157 | if v in v_dic: 158 | v_cou = v_dic[v] 159 | else: 160 | v_cou = len(v_list) 161 | v_dic[v] = v_cou 162 | v_list.append(me.vertices[v].co) 163 | v_.append(v_cou) 164 | 165 | f_list.append(v_) 166 | 167 | bpy.ops.object.mode_set(mode='OBJECT') 168 | bpy.ops.object.select_all(action='DESELECT') 169 | obj = createMeshFromData(name='tmp_QQ', origin=origins, verts=v_list, faces=f_list) 170 | return obj 171 | 172 | 173 | def rotateZ(me): 174 | lv = (me.vertices[0].co, 0) 175 | bv = (me.vertices[0].co, 0) 176 | tv = (me.vertices[0].co, 0) 177 | 178 | for i,v in enumerate(me.vertices[1:]): 179 | if v.co.x < lv[0].x: lv = (v.co, i) 180 | if v.co.y > tv[0].y: tv = (v.co, i) 181 | if v.co.y < bv[0].y: bv = (v.co, i) 182 | 183 | medium = (tv[0].y + bv[0].y)/2 184 | v1 = bv[0] 185 | v2 = lv[0] 186 | if lv[0].y>medium: 187 | v1 = lv[0] 188 | v2 = tv[0] 189 | if lv[1]==bv[1]: 190 | v1 = lv[0] 191 | v2 = tv[0] 192 | 193 | vec_ = v2-v1 194 | mat, neg_mat = vecscorrect(vec=vec_, vec0 = Vector((0,1,1e-6))) 195 | return mat, neg_mat 196 | 197 | 198 | def main(context): 199 | bpy.ops.object.editmode_toggle() 200 | bpy.ops.object.editmode_toggle() 201 | bpy.ops.object.mode_set(mode='EDIT') 202 | bpy.ops.mesh.select_mode(type='FACE') 203 | 204 | super_obj = bpy.context.active_object 205 | super_me = super_obj.data 206 | fs_super = [p.index for p in super_me.polygons if p.select] 207 | obs = [] 208 | if fs_super: 209 | for f_super in fs_super: 210 | qquads(f_super) 211 | obs.append(bpy.context.active_object.name) 212 | bpy.ops.object.select_all(action='DESELECT') 213 | super_obj.select = True 214 | bpy.context.scene.objects.active = super_obj 215 | 216 | bpy.ops.object.select_all(action='DESELECT') 217 | for o in obs: 218 | obj = bpy.data.objects[o] 219 | obj.select = True 220 | bpy.context.scene.objects.active = obj 221 | bpy.ops.object.join() 222 | bpy.ops.object.mode_set(mode='EDIT') 223 | bpy.ops.mesh.select_mode(type='FACE') 224 | bpy.ops.mesh.select_all(action='SELECT') 225 | bpy.ops.mesh.remove_doubles() 226 | bpy.ops.mesh.select_all(action='DESELECT') 227 | 228 | 229 | def qquads(f_super): 230 | maloe = 1e-5 231 | 232 | bpy.ops.object.mode_set(mode='EDIT') 233 | bpy.ops.mesh.select_mode(type='FACE') 234 | 235 | #print('\n ********* start ********') 236 | 237 | obj = bpy.context.active_object 238 | me = obj.data 239 | 240 | bpy.ops.object.mode_set(mode='OBJECT') 241 | 242 | face = f_super 243 | ob = select_planar(me, face) 244 | ob.matrix_world = obj.matrix_world.copy() 245 | me = ob.data 246 | 247 | bpy.ops.object.mode_set(mode='EDIT') 248 | bpy.ops.mesh.select_mode(type='VERT') 249 | bpy.ops.mesh.select_all(action='DESELECT') 250 | bpy.ops.mesh.select_non_manifold(extend=True) 251 | bpy.ops.object.mode_set(mode='OBJECT') 252 | face = 0 253 | 254 | normal = me.polygons[face].normal 255 | mat, neg_mat = vecscorrect(normal, vec0 = Vector((1e-6,1e-6,1))) 256 | 257 | for i,v in enumerate(me.vertices): 258 | me.vertices[i].co = v.co*mat 259 | 260 | matz, neg_matz = rotateZ(me) 261 | for i,v in enumerate(me.vertices): 262 | me.vertices[i].co = v.co*matz 263 | 264 | bpy.ops.object.mode_set(mode='EDIT') 265 | vs_all = [v.index for v in me.vertices if v.select] 266 | 267 | loop_all = getLoops(me, vs_all) 268 | loop_q = [] 269 | loop_sq = [] 270 | lx = [] 271 | ly = [] 272 | 273 | # оптимизировать прямоугольники 274 | for i,loop in enumerate(loop_all): 275 | list_x = [] 276 | list_y = [] 277 | for la in loop: 278 | list_x.append(me.vertices[la].co.x) 279 | list_y.append(me.vertices[la].co.y) 280 | 281 | list_x = set(list_x) 282 | list_y = set(list_y) 283 | 284 | c_left = min(list_x) 285 | c_right = max(list_x) 286 | c_top = max(list_y) 287 | c_bottom = min(list_y) 288 | 289 | vlb = Vector((c_left, c_bottom, 0)) 290 | vlt = Vector((c_left, c_top, 0)) 291 | vrt = Vector((c_right, c_top, 0)) 292 | vrb = Vector((c_right, c_bottom, 0)) 293 | 294 | sq = vrb - vlb 295 | lx.extend([c_left, c_right]) 296 | ly.extend([c_top, c_bottom]) 297 | 298 | loop_sq.append((sq,i)) 299 | loop_q.append([vlb, vlt, vrt, vrb]) 300 | 301 | # Получить каркас А 302 | loop_A = max(loop_sq)[1] 303 | 304 | for ix,x in enumerate(lx): 305 | if xloop_q[loop_A][3].x: lx[ix]=loop_q[loop_A][3].x 307 | 308 | for iy,y in enumerate(ly): 309 | if yloop_q[loop_A][2].y: ly[iy]=loop_q[loop_A][2].y 311 | 312 | # Разложить сетку 313 | lx.sort() 314 | ly.sort() 315 | 316 | lx_ = [] 317 | ly_ = [] 318 | for ix,x in enumerate(lx[:-1]): 319 | if abs(x-lx[ix+1])>maloe: lx_.append(x) 320 | for iy,y in enumerate(ly[:-1]): 321 | if abs(y-ly[iy+1])>maloe: ly_.append(y) 322 | lx_.append(lx[-1]) 323 | ly_.append(ly[-1]) 324 | 325 | lx = lx_ 326 | ly = ly_ 327 | loop_x = [] 328 | verts = [] 329 | faces = [] 330 | for ix,vx in enumerate(lx[:-1]): 331 | v = Vector((vx, ly[0], loop_q[loop_A][0].z)) 332 | idx = len(verts) 333 | verts.append(v) 334 | loop_x.append(idx) 335 | 336 | 337 | 338 | extr_rt = len(verts) 339 | verts.append(Vector((lx[-1], ly[0], loop_q[loop_A][0].z))) 340 | for y in ly[1:]: 341 | tmp_x = [] 342 | num_lt = len(verts) 343 | verts.append(Vector((verts[loop_x[0]].x, y, verts[loop_x[0]].z))) 344 | 345 | for ix, line_x in enumerate(loop_x): 346 | if (verts[line_x].x-lx[ix])>maloe: 347 | loop_x.insert(ix+1, line_x) 348 | continue 349 | 350 | xx=(verts[line_x].x+lx[ix+1])/2 351 | yy=(verts[line_x].y+y)/2 352 | 353 | flag = True 354 | for i,loop in enumerate(loop_q): 355 | if i==loop_A: continue 356 | 357 | if yy>loop[0].y and yyloop[0].x and xx-1: 391 | tmp_x.append(num_lt) 392 | num_lt = -1 393 | 394 | loop_x = tmp_x.copy() 395 | 396 | bpy.ops.mesh.select_all(action='DESELECT') 397 | bpy.ops.object.mode_set(mode='OBJECT') 398 | 399 | dz_ = me.vertices[0].co.z 400 | bpy.ops.object.select_all(action='DESELECT') 401 | 402 | ob.select = True 403 | bpy.ops.object.delete() 404 | 405 | ob = createMeshFromData(name='retopo object', origin=Vector((0,0,0)), verts=verts, faces=faces) 406 | ob.matrix_world = obj.matrix_world.copy() 407 | me = ob.data 408 | 409 | dz = dz_ - me.vertices[0].co.z 410 | for i,v in enumerate(me.vertices): 411 | me.vertices[i].co.z = v.co.z+dz 412 | me.vertices[i].co = v.co*neg_matz*neg_mat 413 | 414 | 415 | class QquadOperator(bpy.types.Operator): 416 | bl_idname = "mesh.qquad" 417 | bl_label = "QQUAD" 418 | bl_options = {'REGISTER', 'UNDO', 'PRESET'} 419 | 420 | @classmethod 421 | def poll(cls, context): 422 | return context.active_object is not None 423 | 424 | def execute(self, context): 425 | main(context) 426 | return {'FINISHED'} 427 | 428 | def register(): 429 | bpy.utils.register_class(QquadOperator) 430 | 431 | 432 | def unregister(): 433 | bpy.utils.unregister_class(QquadOperator) 434 | 435 | 436 | if __name__ == "__main__": 437 | register() -------------------------------------------------------------------------------- /Paul_scrips.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | bl_info = { 4 | "name": "Paul scripts", 5 | "author": "Alexander Nedovizin", 6 | "version": (0, 1, 1), 7 | "blender": (2, 6, 8), 8 | "location": "View3D > Toolbar", 9 | "category": "Mesh" 10 | } 11 | 12 | 13 | import bpy 14 | 15 | 16 | 17 | def find_index_of_selected_vertex(mesh): 18 | selected_verts = [i.index for i in mesh.vertices if i.select] 19 | verts_selected = len(selected_verts) 20 | if verts_selected <1: 21 | return None 22 | else: 23 | return selected_verts 24 | 25 | 26 | def find_extreme_select_verts(mesh, verts_idx): 27 | res_vs = [] 28 | edges = mesh.edges 29 | 30 | for v_idx in verts_idx: 31 | connecting_edges = [i for i in edges if v_idx in i.vertices[:] and i.select] 32 | if len(connecting_edges) == 1: 33 | res_vs.append(v_idx) 34 | return res_vs 35 | 36 | 37 | def find_connected_verts(me, found_index, not_list): 38 | edges = me.edges 39 | connecting_edges = [i for i in edges if found_index in i.vertices[:]] 40 | if len(connecting_edges) == 0: 41 | return [] 42 | else: 43 | connected_verts = [] 44 | for edge in connecting_edges: 45 | cvert = set(edge.vertices[:]) 46 | cvert.remove(found_index) 47 | vert = cvert.pop() 48 | if not (vert in not_list) and me.vertices[vert].select: 49 | connected_verts.append(vert) 50 | return connected_verts 51 | 52 | 53 | def find_all_connected_verts(me, active_v, not_list=[], step=0): 54 | vlist = [active_v] 55 | not_list.append(active_v) 56 | step+=1 57 | list_v_1 = find_connected_verts(me, active_v, not_list) 58 | 59 | for v in list_v_1: 60 | list_v_2 = find_all_connected_verts(me, v, not_list, step) 61 | vlist += list_v_2 62 | return vlist 63 | 64 | 65 | def main_spread(context, mode): 66 | bpy.ops.object.mode_set(mode='OBJECT') 67 | bpy.ops.object.mode_set(mode='EDIT') 68 | 69 | obj = bpy.context.active_object 70 | me = obj.data 71 | 72 | verts = find_index_of_selected_vertex(me) 73 | cou_vs = len(verts) - 1 74 | if verts != None and cou_vs>0: 75 | extreme_vs = find_extreme_select_verts(me, verts) 76 | 77 | if len(extreme_vs) != 2: 78 | print_error('Надо задавать один луп!') 79 | print('Error: 01') 80 | return False 81 | 82 | if mode[0]: 83 | min_v = min([me.vertices[extreme_vs[0]].co.x,extreme_vs[0]], \ 84 | [me.vertices[extreme_vs[1]].co.x,extreme_vs[1]]) 85 | max_v = max([me.vertices[extreme_vs[0]].co.x,extreme_vs[0]], \ 86 | [me.vertices[extreme_vs[1]].co.x,extreme_vs[1]]) 87 | 88 | if (max_v[0]-min_v[0]) == 0: 89 | min_v = [me.vertices[extreme_vs[0]].co.x,extreme_vs[0]] 90 | max_v = [me.vertices[extreme_vs[1]].co.x,extreme_vs[1]] 91 | 92 | sort_list = find_all_connected_verts(me,min_v[1],[]) 93 | 94 | if len(sort_list) != len(verts): 95 | print_error('Разоравнный луп!') 96 | print('Error: 020') 97 | return False 98 | 99 | step = [] 100 | if mode[3]: 101 | list_length = [] 102 | sum_length = 0.0 103 | x_sum = 0.0 104 | for sl in range(cou_vs): 105 | subb = me.vertices[sort_list[sl+1]].co-me.vertices[sort_list[sl]].co 106 | length = subb.length 107 | sum_length += length 108 | list_length.append(sum_length) 109 | x_sum += subb.x 110 | 111 | for sl in range(cou_vs): 112 | step.append(x_sum * list_length[sl]/sum_length) 113 | else: 114 | diap = (max_v[0]-min_v[0])/cou_vs 115 | for sl in range(cou_vs): 116 | step.append((sl+1)*diap) 117 | 118 | bpy.ops.object.mode_set(mode='OBJECT') 119 | for idx in range(cou_vs): 120 | me.vertices[sort_list[idx+1]].co.x = me.vertices[sort_list[0]].co.x + step[idx] 121 | 122 | bpy.ops.object.mode_set(mode='EDIT') 123 | 124 | if mode[1]: 125 | min_v = min([me.vertices[extreme_vs[0]].co.y,extreme_vs[0]], \ 126 | [me.vertices[extreme_vs[1]].co.y,extreme_vs[1]]) 127 | max_v = max([me.vertices[extreme_vs[0]].co.y,extreme_vs[0]], \ 128 | [me.vertices[extreme_vs[1]].co.y,extreme_vs[1]]) 129 | 130 | if (max_v[0]-min_v[0]) == 0: 131 | min_v = [me.vertices[extreme_vs[0]].co.y,extreme_vs[0]] 132 | max_v = [me.vertices[extreme_vs[1]].co.y,extreme_vs[1]] 133 | 134 | sort_list = find_all_connected_verts(me,min_v[1],[]) 135 | if len(sort_list) != len(verts): 136 | print_error('Разоравнный луп!') 137 | print('Error: 021') 138 | return False 139 | 140 | step = [] 141 | if mode[3]: 142 | list_length = [] 143 | sum_length = 0.0 144 | y_sum = 0.0 145 | for sl in range(cou_vs): 146 | subb = me.vertices[sort_list[sl+1]].co-me.vertices[sort_list[sl]].co 147 | length = subb.length 148 | sum_length += length 149 | list_length.append(sum_length) 150 | y_sum += subb.y 151 | 152 | for sl in range(cou_vs): 153 | step.append(y_sum * list_length[sl]/sum_length) 154 | else: 155 | diap = (max_v[0]-min_v[0])/cou_vs 156 | for sl in range(cou_vs): 157 | step.append((sl+1)*diap) 158 | 159 | bpy.ops.object.mode_set(mode='OBJECT') 160 | for idx in range(cou_vs): 161 | me.vertices[sort_list[idx+1]].co.y = me.vertices[sort_list[0]].co.y + step[idx] 162 | 163 | bpy.ops.object.mode_set(mode='EDIT') 164 | 165 | if mode[2]: 166 | min_v = min([me.vertices[extreme_vs[0]].co.z,extreme_vs[0]], \ 167 | [me.vertices[extreme_vs[1]].co.z,extreme_vs[1]]) 168 | max_v = max([me.vertices[extreme_vs[0]].co.z,extreme_vs[0]], \ 169 | [me.vertices[extreme_vs[1]].co.z,extreme_vs[1]]) 170 | 171 | if (max_v[0]-min_v[0]) == 0: 172 | min_v = [me.vertices[extreme_vs[0]].co.z,extreme_vs[0]] 173 | max_v = [me.vertices[extreme_vs[1]].co.z,extreme_vs[1]] 174 | 175 | sort_list = find_all_connected_verts(me,min_v[1],[]) 176 | if len(sort_list) != len(verts): 177 | print_error('Разоравнный луп!') 178 | print('Error: 022') 179 | return False 180 | 181 | step = [] 182 | if mode[3]: 183 | list_length = [] 184 | sum_length = 0.0 185 | z_sum = 0.0 186 | for sl in range(cou_vs): 187 | subb = me.vertices[sort_list[sl+1]].co-me.vertices[sort_list[sl]].co 188 | length = subb.length 189 | sum_length += length 190 | list_length.append(sum_length) 191 | z_sum += subb.z 192 | 193 | for sl in range(cou_vs): 194 | step.append(z_sum * list_length[sl]/sum_length) 195 | else: 196 | diap = (max_v[0]-min_v[0])/cou_vs 197 | for sl in range(cou_vs): 198 | step.append((sl+1)*diap) 199 | 200 | bpy.ops.object.mode_set(mode='OBJECT') 201 | for idx in range(cou_vs): 202 | me.vertices[sort_list[idx+1]].co.z = me.vertices[sort_list[0]].co.z + step[idx] 203 | 204 | bpy.ops.object.mode_set(mode='EDIT') 205 | 206 | return True 207 | 208 | 209 | def main_ss(context): 210 | obj = bpy.context.active_object 211 | me = obj.data 212 | 213 | bpy.ops.object.mode_set(mode='OBJECT') 214 | bpy.ops.object.mode_set(mode='EDIT') 215 | 216 | vs_idx = find_index_of_selected_vertex(me) 217 | if vs_idx: 218 | x_coos = [v.co.x for v in me.vertices if v.index in vs_idx] 219 | y_coos = [v.co.y for v in me.vertices if v.index in vs_idx] 220 | 221 | min_x = min(x_coos) 222 | max_x = max(x_coos) 223 | 224 | min_y = min(y_coos) 225 | max_y = max(y_coos) 226 | 227 | len_x = max_x-min_x 228 | len_y = max_y-min_y 229 | 230 | if len_y CustomNodesTree > Add user nodes", 12 | "description": "spreads donor on recipient object using nodes system", 13 | "warning": "requires nodes window", 14 | "wiki_url": "", 15 | "tracker_url": "", 16 | "category": "Node", 17 | } 18 | 19 | import bpy, copy 20 | from mathutils.geometry import intersect_line_plane 21 | import mathutils 22 | import math 23 | import parser 24 | from bpy.props import IntProperty, FloatProperty, StringProperty 25 | from bpy_types import NodeTree, Node, NodeSocket 26 | 27 | 28 | per_cache = {} 29 | 30 | # force a full recalculation next time 31 | def cache_delete(cache): 32 | if cache in per_cache: 33 | 34 | if 'spreads' in per_cache[cache]: 35 | bpy.ops.object.select_all(action='DESELECT') 36 | for sh in per_cache[cache]['spreads']: 37 | obj = bpy.data.objects[sh[0]] 38 | obj.select = True 39 | 40 | bpy.ops.object.delete() 41 | del per_cache[cache]['spreads'] 42 | 43 | del per_cache[cache] 44 | 45 | def cache_read(cache): 46 | # current tool not cached yet 47 | if not (cache in per_cache): 48 | return(False, False, False, False, False, False, False, False) 49 | 50 | recipient = per_cache[cache]["recipient"] 51 | donor = per_cache[cache]["donor"] 52 | centres = per_cache[cache]["centres"] 53 | idx_centre = per_cache[cache]["idx_centre"] 54 | formula = per_cache[cache]["formula"] 55 | diap_min = per_cache[cache]["diap_min"] 56 | diap_max = per_cache[cache]["diap_max"] 57 | 58 | return(True, recipient, donor, centres, idx_centre, formula, diap_min, diap_max) 59 | 60 | 61 | # store information in the cache 62 | def cache_write(cache, recipient, donor, centres, idx_centre, formula, diap_min, diap_max): 63 | # clear cache of current tool 64 | if cache in per_cache: 65 | #del per_cache[cache] 66 | if recipient != per_cache[cache]['recipient']: cache_delete(cache) 67 | elif donor != per_cache[cache]['donor']: cache_delete(cache) 68 | elif centres != per_cache[cache]['centres']: cache_delete(cache) 69 | elif formula != per_cache[cache]['formula']: cache_delete(cache) 70 | elif formula != per_cache[cache]['diap_min']: cache_delete(cache) 71 | elif formula != per_cache[cache]['diap_max']: cache_delete(cache) 72 | 73 | # update cache 74 | if not (cache in per_cache) and cache!='': 75 | per_cache[cache] = {"recipient": recipient, "donor": donor, 76 | "centres": centres, "idx_centre": idx_centre, "formula": formula, 77 | "diap_min":diap_min, "diap_max":diap_max} 78 | 79 | 80 | 81 | 82 | 83 | 84 | class RecepCustomTree(NodeTree): 85 | '''A custom node tree type that will show up in the node editor header''' 86 | bl_idname = 'RecepCustomTreeType' 87 | bl_label = 'Custom Node Tree' 88 | bl_icon = 'NODETREE' 89 | 90 | 91 | class DonorSocket(NodeSocket): 92 | '''Donor Socket_type''' 93 | bl_idname = "DonorSocket" 94 | bl_label = "Donor Socket" 95 | 96 | DonorProperty = bpy.props.StringProperty(name="DonorProperty") 97 | 98 | def draw(self, context, layout, node, text): 99 | if self.is_linked: 100 | layout.label(text) 101 | else: 102 | col = layout.column(align=True) 103 | row = col.row(align=True) 104 | row.prop(self, 'DonorProperty', text=text) 105 | 106 | def draw_color(self, context, node): 107 | return(.8,.3,.75,1.0) 108 | 109 | 110 | 111 | class RecepCustomTreeNode : 112 | @classmethod 113 | def poll(cls, ntree): 114 | return ntree.bl_idname == 'RecepCustomTreeType' 115 | 116 | 117 | def object_select(self, context): 118 | '''Return tuples of objects in scene for enum props''' 119 | return [tuple(3 * [ob.name]) for ob in context.scene.objects \ 120 | if (ob.type == 'MESH' or ob.type == 'EMPTY') and ob.name.split('.')[0]!='spread'] 121 | 122 | 123 | class DonorInput(Node, RecepCustomTreeNode): 124 | ''' DonorInput''' 125 | bl_idname = 'DonorInputNode' 126 | bl_label = 'Object input' 127 | bl_icon = 'OUTLINER_OB_EMPTY' 128 | 129 | ObjectProperty = bpy.props.EnumProperty(items=object_select) 130 | 131 | def init(self, context): 132 | self.outputs.new('DonorSocket', "Object") 133 | 134 | def draw_buttons(self, context, layout): 135 | layout.prop(self, "ObjectProperty", text="Object", icon='OBJECT_DATA') 136 | 137 | def update(self): 138 | if self.ObjectProperty and self.ObjectProperty in bpy.context.scene.objects \ 139 | and len(self.outputs['Object'].links)>0: 140 | object = bpy.data.objects[self.ObjectProperty] 141 | self.outputs['Object'].DonorProperty = object.name 142 | 143 | def update_socket(self, context): 144 | self.update() 145 | 146 | 147 | class SurfOutput(Node, RecepCustomTreeNode): 148 | ''' Surface output node for basics definition ''' 149 | bl_idname = 'SurfOutputNode' 150 | bl_label = 'Surface object' 151 | bl_icon = 'OUTLINER_OB_EMPTY' 152 | 153 | cache = bpy.props.StringProperty(default='') 154 | 155 | ObjectProperty = bpy.props.EnumProperty(items=object_select) 156 | FormulaProperty = bpy.props.StringProperty(default='1/x', description = 'write your spread\'s scale formula, using X as distance variable') 157 | 158 | def init(self, context): 159 | self.inputs.new('DonorSocket', "Donor") 160 | self.inputs.new('DonorSocket', "Centres") 161 | self.inputs.new('NodeSocketFloat', "Min","MinScale") 162 | self.inputs.new('NodeSocketFloat', "Max","MaxScale").default_value = 1.0 163 | self.outputs.new('DonorSocket', "List Objects", "ListObjects") 164 | 165 | def draw_buttons(self, context, layout): 166 | layout.prop(self, "ObjectProperty", text="Object", icon='OBJECT_DATA') 167 | layout.prop(self, "FormulaProperty", text="Formula") 168 | 169 | 170 | if self.ObjectProperty and self.ObjectProperty in bpy.context.scene.objects \ 171 | and len(self.inputs['Donor'].links)>0 \ 172 | and len(self.inputs['Centres'].links)>0: 173 | 174 | layout.operator("node.sverchok_spread", text='Update').cache = self.name 175 | 176 | 177 | def update(self): 178 | if self.ObjectProperty and self.ObjectProperty in bpy.context.scene.objects \ 179 | and len(self.inputs['Donor'].links)>0 \ 180 | and len(self.inputs['Centres'].links)>0: 181 | 182 | object = bpy.data.objects[self.ObjectProperty] 183 | tmpDonor = self.inputs['Donor'].links[0].from_socket.DonorProperty.split() 184 | 185 | object = self.ObjectProperty 186 | donor = tmpDonor[0] 187 | centres = self.inputs['Centres'].links[0].from_socket.DonorProperty 188 | idx_centre = 0 189 | formula = self.FormulaProperty 190 | diap_min = self.inputs['MinScale'].default_value 191 | diap_max = self.inputs['MaxScale'].default_value 192 | 193 | cache_write(self.name, object, donor, centres, idx_centre, formula, diap_min, diap_max) 194 | 195 | def update_socket(self, context): 196 | #print('update soket surf') 197 | self.update() 198 | 199 | 200 | class ListObjectsNode(Node, RecepCustomTreeNode): 201 | '''A custom node''' 202 | bl_idname = 'ListObjectsNode' 203 | bl_label = 'List Objects' 204 | bl_icon = 'SOUND' 205 | 206 | 207 | def init(self, context): 208 | self.inputs.new('DonorSocket', "Object 1", "Object1") 209 | self.inputs.new('DonorSocket', "Object 2", "Object2") 210 | self.inputs.new('DonorSocket', "Object 3", "Object3") 211 | self.inputs.new('DonorSocket', "Object 4", "Object4") 212 | 213 | self.outputs.new('DonorSocket', "List Objects", "ListObjects") 214 | 215 | # Copy function to initialize a copied node from an existing one. 216 | def copy(self, node): 217 | print("Copying from node ", node) 218 | 219 | # Free function to clean up on removal. 220 | def free(self): 221 | print("Removing node ", self, ", Goodbye!") 222 | 223 | def update(self): 224 | (s1,s2,s3,s4) = ('','','','') 225 | if len(self.inputs['Object1'].links)>0: 226 | s1 = self.inputs['Object1'].links[0].from_socket.DonorProperty + ' ' 227 | 228 | if len(self.inputs['Object2'].links)>0: 229 | s2 = self.inputs['Object2'].links[0].from_socket.DonorProperty + ' ' 230 | 231 | if len(self.inputs['Object3'].links)>0: 232 | s3 = self.inputs['Object3'].links[0].from_socket.DonorProperty + ' ' 233 | 234 | if len(self.inputs['Object4'].links)>0: 235 | s4 = self.inputs['Object4'].links[0].from_socket.DonorProperty + ' ' 236 | 237 | s = s1+s2+s3+s4 238 | s = s.rstrip() 239 | 240 | self.outputs["ListObjects"].DonorProperty = s 241 | 242 | 243 | def update_socket(self, context): 244 | #print('update soket surf') 245 | self.update() 246 | 247 | 248 | ### Node Categories ### 249 | # Node categories are a python system for automatically 250 | # extending the Add menu, toolbar panels and search operator. 251 | # For more examples see release/scripts/startup/nodeitems_builtins.py 252 | 253 | import nodeitems_utils 254 | from nodeitems_utils import NodeCategory, NodeItem 255 | 256 | # our own base class with an appropriate poll function, 257 | # so the categories only show up in our own tree type 258 | class RecepNodeCategory(NodeCategory): 259 | @classmethod 260 | def poll(cls, context): 261 | return context.space_data.tree_type == 'RecepCustomTreeType' 262 | 263 | # all categories in a list 264 | node_categories = [ 265 | # identifier, label, items list 266 | RecepNodeCategory("SOMENODES", "Some Nodes", items=[ 267 | # our basic node 268 | NodeItem("SurfOutputNode", label="Surface object"), 269 | NodeItem("DonorInputNode", label="Object input"), 270 | NodeItem("ListObjectsNode", label="List Objects"), 271 | ]), 272 | ] 273 | 274 | 275 | def tree_update(): 276 | for ng in bpy.context.blend_data.node_groups: 277 | ng.interface_update(bpy.context) 278 | return 279 | 280 | 281 | 282 | def vec_pols(pol2,ver2,mw2): 283 | vector_mediana = [] 284 | for p in pol2: 285 | vs_idx = p.vertices 286 | 287 | v0 = ver2[vs_idx[0]].co 288 | v1 = ver2[vs_idx[1]].co 289 | v2 = ver2[vs_idx[2]].co 290 | v3 = ver2[vs_idx[3]].co 291 | 292 | point1 = (v0+v1)/2 293 | point2 = (v2+v3)/2 294 | vm = point2 - point1 295 | 296 | vector_mediana.append(vm) 297 | return vector_mediana 298 | 299 | 300 | def cont_obj(ob): 301 | (l,r,f,b,t,d) = (0,0,0,0,0,0) 302 | for v in ob.data.vertices: 303 | if v.co[0]r: r = v.co[0] 305 | if v.co[1]f: f = v.co[1] 307 | if v.co[2]>t: t = v.co[2] 308 | if v.co[2] Tools > Doorway", 9 | "description": "Create doorway between the two parallel planes", 10 | "warning": "", 11 | "wiki_url": "", 12 | "tracker_url": "", 13 | "category": "Mesh"} 14 | 15 | 16 | import bpy, bmesh 17 | from copy import copy 18 | from bpy.props import StringProperty 19 | import mathutils 20 | 21 | 22 | 23 | 24 | 25 | ''' 26 | refs: 27 | http://www.blender.org/documentation/blender_python_api_2_68_release/mathutils.geometry.html 28 | http://www.blender.org/documentation/blender_python_api_2_68_release/bmesh.ops.html#module-bmesh.ops 29 | http://www.mathprofi.ru/uravnenie_pryamoi_na_ploskosti.html 30 | ''' 31 | 32 | 33 | def verts_on_planes(verts, pols): 34 | ''' 35 | IN - [verts], [polygons] 36 | OUT - [{polygon index:[verts index]}] 37 | ''' 38 | 39 | result = [] 40 | for i,p in enumerate(pols): 41 | cnt = p.center 42 | vc = mathutils.Vector((cnt)) 43 | N = pols[i].normal 44 | vs_idx = [] 45 | 46 | for j,v in enumerate(verts): 47 | k = N*(vc-v.co) 48 | if (abs(k) < 0.01): 49 | vs_idx.append(j) 50 | if len(vs_idx)>0: 51 | result.append({i:vs_idx}) 52 | return result 53 | 54 | 55 | def equation_plane(point, normal_dest): 56 | #получаем коэффициенты уравнения плоскости по точке и нормали 57 | normal = normal_dest.normalized() 58 | A = normal.x 59 | B = normal.y 60 | C = normal.z 61 | D = (A*point.x+B*point.y+C*point.z)*-1 62 | return (A,B,C,D) 63 | 64 | 65 | def update_lt(self, context): 66 | if self.drew_it: 67 | bpy.ops.doorway.doorway() 68 | bpy.ops.object.mode_set(mode='OBJECT') 69 | 70 | 71 | 72 | def main_draw(context): 73 | lt = bpy.context.window_manager.doorway_manager 74 | #взять нормаль полскости 75 | ##принадлежит ли точка плоскости 76 | obj = bpy.context.active_object 77 | pols = obj.data.polygons 78 | verts = obj.data.vertices 79 | 80 | bpy.ops.object.mode_set(mode='OBJECT') 81 | 82 | mods_bool = [] 83 | for mod in obj.modifiers: 84 | if mod.type == "BOOLEAN": 85 | mods_bool.append(mod.name) 86 | len_bm = len(mods_bool) 87 | 88 | bpy.ops.object.mode_set(mode='EDIT') 89 | bpy.ops.mesh.select_mode(type='FACE') 90 | obj.data.update() 91 | 92 | #************************** 93 | if len_bm<=lt.mods_bool_count: 94 | act_face = [] 95 | bm = bmesh.from_edit_mesh(obj.data) 96 | for elem in bm.select_history: 97 | if isinstance(elem, bmesh.types.BMFace): 98 | act_face.append(pols[elem.index]) 99 | 100 | R = {} 101 | quad = () 102 | gp = bpy.context.object.grease_pencil 103 | for idx, stroke in enumerate(gp.layers.active.active_frame.strokes): 104 | stroke = gp.layers.active.active_frame.strokes[-1] 105 | gverts = stroke.points 106 | R = verts_on_planes(gverts, pols) 107 | 108 | if len(R)==0: 109 | #показать предупреждение 110 | print_error('Нет карандаша на активном объекте!') 111 | bpy.ops.object.mode_set(mode='OBJECT') 112 | #print('Error: 02') 113 | return False 114 | 115 | #теперь надо получить результирующий вектор 116 | idx_face_0 = list(R[0].keys())[0] 117 | if len(R)>0: 118 | v1 = R[0][idx_face_0][0] 119 | v2 = R[0][idx_face_0][-1] 120 | 121 | vec_diag = gverts[v2].co-gverts[v1].co 122 | quad = (gverts[v1].co, gverts[v2].co, pols[idx_face_0].index) 123 | bm.free 124 | 125 | if len(quad)==0: 126 | print_error('Нет карандаша на активном объекте!') 127 | bpy.ops.object.mode_set(mode='OBJECT') 128 | #print('Error: 03') 129 | return False 130 | 131 | #надо получить противопложную плоскость 132 | pol = pols[quad[2]] 133 | pol_normal = copy(pol.normal) 134 | vec_back = pol_normal * -1 135 | 136 | point_mid_1 = (quad[0] + quad[1])/2 137 | ep = equation_plane(verts[pol.vertices[0]].co, pol_normal) 138 | 139 | p_idx = -1 140 | dist = -1 141 | 142 | if pol_normal.length<1e-6: 143 | doorway_managerProps.mods_bool_count = 100 144 | print_error('Нарисуйте линию и повторите попытку.') 145 | print('Error: 01') 146 | return False 147 | 148 | for i,p in enumerate(pols): 149 | if i == quad[2]: continue 150 | 151 | if not lt.check_parallel or (vec_back - p.normal).length<1e-6: 152 | vs0 = verts[p.vertices[0]].co 153 | #получаем расстояние между плоскостями 154 | disttmp = abs(vs0.x*ep[0] + vs0.y*ep[1] + vs0.z*ep[2] + ep[3])/pol_normal.length 155 | #найти точку пересечения с этой плоскостью 156 | vec_back.normalize() 157 | point_mid_2 = point_mid_1 + vec_back*disttmp 158 | #определяем эта точка находится внутри полигона или нет. 159 | cnt2 = mathutils.Vector(p.center) 160 | sum_cnt2 = 0 161 | sum_pm2 = 0 162 | for j,v_idx in enumerate(p.vertices): 163 | v1 = verts[v_idx].co 164 | l_pm2 = (v1-point_mid_2).length 165 | l_cnt2 = (v1 - cnt2).length 166 | 167 | sum_pm2 += l_pm2 168 | sum_cnt2 += l_cnt2 169 | 170 | if abs(sum_pm2-sum_cnt2)>2: 171 | #вектор НЕ попадает в полигон2 172 | continue 173 | 174 | if dist>disttmp or dist<0: 175 | p_idx = i 176 | dist = disttmp 177 | 178 | if p_idx<0: 179 | #Сообщить, что искали,но не нашли 180 | print_error('Нет подходящего противоположного полигона.') 181 | bpy.ops.object.mode_set(mode='OBJECT') 182 | print('Error: 04') 183 | return False 184 | 185 | #плоскость найдена 186 | #dist - расстояние 187 | #p_idx - индекс плоскости2 188 | #ep - уровнение плоскости1 189 | #vec_diag - вектор-диагональ 190 | #pol_normal 191 | 192 | bpy.ops.object.mode_set(mode='OBJECT') 193 | 194 | if len_bm<=lt.mods_bool_count: 195 | #Первый раз рисуем куб 196 | bpy.ops.mesh.primitive_cube_add(location=(0.0,0.0,0.0)) 197 | cube = bpy.context.active_object 198 | 199 | #удалить грани нормали которых параллельны оси y 200 | bpy.ops.object.mode_set(mode='EDIT') 201 | pols_cube = cube.data.polygons 202 | verts_cube = cube.data.vertices 203 | napr = mathutils.Vector((0.0,1.0,0.0)) 204 | bpy.ops.mesh.select_all(action='DESELECT') 205 | bpy.ops.object.mode_set(mode='OBJECT') 206 | 207 | for i,p in enumerate(pols_cube): 208 | nm_p_cube = p.normal 209 | mul = napr * nm_p_cube 210 | if mul!=0.0: 211 | #векторы параллельны, т.к не перпендикулярны (в кубе третьего не дано) 212 | p.select = True 213 | 214 | bpy.ops.object.mode_set(mode='EDIT') 215 | bpy.ops.mesh.delete(type='FACE') 216 | 217 | #отмасштабировать и развернуть 218 | bpy.ops.mesh.select_all(action='SELECT') 219 | bpy.ops.object.mode_set(mode='OBJECT') 220 | 221 | #получить длину и ширину проёма 222 | aa = mathutils.Vector((0.0,1.0,0.0)) 223 | bb = pol_normal 224 | vec = aa 225 | q_rot = vec.rotation_difference(bb).to_matrix().to_4x4() 226 | vec_scale_cube = vec_diag*q_rot 227 | vec_scale_cube.y = dist+0.5 228 | vec_scale_cube.x = abs(vec_scale_cube.x) 229 | vec_scale_cube.z = abs(vec_scale_cube.z) 230 | 231 | bm = bmesh.new() 232 | bm.from_mesh(cube.data) 233 | 234 | if lt.size_width!=0.0: 235 | vec_scale_cube.x = abs(lt.size_width)*vec_scale_cube.x /abs(vec_scale_cube.x ) 236 | 237 | if lt.size_height!=0.0: 238 | vec_scale_cube.z = abs(lt.size_height)*vec_scale_cube.z/abs(vec_scale_cube.z) 239 | 240 | vec_scale_cube /= 2 241 | bmesh.ops.scale(bm, vec=vec_scale_cube, verts=bm.verts) 242 | 243 | bm.to_mesh(cube.data) 244 | bm.free 245 | 246 | #развернуть куб в плоскость Плоскость1 247 | aa = mathutils.Vector((0.0,1.0,0.0)) 248 | bb = pol_normal 249 | vec = aa 250 | q_rot = vec.rotation_difference(bb).to_matrix().to_4x4() 251 | cube.matrix_local *= q_rot 252 | 253 | #выставить в проём 254 | vec_delta_trans = mathutils.Vector((lt.delta_width, 0, lt.delta_height)) 255 | mat_delta_trans = mathutils.Matrix.Translation(vec_delta_trans) 256 | 257 | tmpvec_proem = point_mid_1+vec_back*(dist-0.2)/2 258 | bpy.ops.transform.translate(value = tmpvec_proem) 259 | 260 | list_mat = list(cube.matrix_local) 261 | list_al_arr = list(map(lambda x:list(x),list_mat)) 262 | 263 | mat_value = bpy.context.window_manager.doorway_matrix 264 | if len(mat_value)==0: 265 | for i in range(16): 266 | mat_value = bpy.context.window_manager.doorway_matrix.add() 267 | mat_value.value = 0.0 268 | mat_value = bpy.context.window_manager.doorway_matrix 269 | 270 | for ii,mv in enumerate(list_al_arr): 271 | for jj,mv_el in enumerate(mv): 272 | mat_value[ii*4+jj].value = mv_el 273 | 274 | mat_current = copy(cube.matrix_local) 275 | cube.matrix_local *= mat_delta_trans 276 | 277 | lt.matrix_x0 = mat_current[0][3] 278 | lt.matrix_y0 = mat_current[2][3] 279 | 280 | bpy.ops.object.mode_set(mode='EDIT') 281 | bpy.ops.object.mode_set(mode='OBJECT') 282 | else: 283 | #куб уже нарисован 284 | cube = bpy.context.scene.objects[lt.cube_str_name] 285 | 286 | mat_value = bpy.context.window_manager.doorway_matrix 287 | mat_v = mathutils.Matrix() 288 | mat_v[0] = mathutils.Vector((mat_value[0].value,mat_value[1].value,mat_value[2].value,mat_value[3].value)) 289 | mat_v[1] = mathutils.Vector((mat_value[4].value,mat_value[5].value,mat_value[6].value,mat_value[7].value)) 290 | mat_v[2] = mathutils.Vector((mat_value[8].value,mat_value[9].value,mat_value[10].value,mat_value[11].value)) 291 | mat_v[3] = mathutils.Vector((mat_value[12].value,mat_value[13].value,mat_value[14].value,mat_value[15].value)) 292 | 293 | cube.matrix_local = mat_v 294 | 295 | vec_delta_trans = mathutils.Vector((lt.delta_width, 0, lt.delta_height)) 296 | mat_delta_trans = mathutils.Matrix.Translation(vec_delta_trans) 297 | 298 | mat_current = copy(cube.matrix_local) 299 | cube.matrix_local *= mat_delta_trans 300 | 301 | lt.matrix_x0 = mat_current[0][3] 302 | lt.matrix_y0 = mat_current[2][3] 303 | 304 | bpy.ops.object.mode_set(mode='EDIT') 305 | bpy.ops.object.mode_set(mode='OBJECT') 306 | 307 | 308 | 309 | if len_bm<=lt.mods_bool_count: 310 | doorway_managerProps.mods_bool_count = len_bm 311 | #вырезаем проем 312 | bpy.ops.object.select_all(action='DESELECT') 313 | bpy.context.scene.objects.active = obj 314 | bpy.ops.object.select_pattern(pattern=obj.name, case_sensitive=False, extend=False) 315 | bpy.ops.object.modifier_add(type='BOOLEAN') 316 | for mod in reversed(obj.modifiers): 317 | if mod.type == "BOOLEAN" and mod.name not in mods_bool: 318 | bool_name = mod.name 319 | break 320 | 321 | bpy.context.object.modifiers[bool_name].operation = 'DIFFERENCE' 322 | bpy.context.object.modifiers[bool_name].object = cube 323 | gp.layers.active.active_frame.strokes.remove(gp.layers.active.active_frame.strokes[-1]) 324 | 325 | else: 326 | mod = obj.modifiers[-1] 327 | mod.object = cube 328 | bool_name = mod.name 329 | 330 | bpy.ops.object.select_all(action='DESELECT') 331 | bpy.context.scene.objects.active = cube 332 | bpy.ops.object.select_pattern(pattern=cube.name, case_sensitive=False, extend=False) 333 | bpy.ops.object.hide_view_set() 334 | 335 | bpy.context.scene.objects.active = obj 336 | bpy.ops.object.select_pattern(pattern=obj.name, case_sensitive=False, extend=False) 337 | bpy.ops.object.mode_set(mode='EDIT') 338 | bpy.ops.mesh.select_all(action='DESELECT') 339 | bpy.ops.object.mode_set(mode='OBJECT') 340 | 341 | doorway_managerProps.cube_str_name = cube.name 342 | doorway_managerProps.obj_str_name = obj.name 343 | doorway_managerProps.drew_it = True 344 | 345 | 346 | 347 | class CreateDoorway(bpy.types.Operator): 348 | bl_idname = "doorway.doorway" 349 | bl_label = "Doorway" 350 | bl_options = {'UNDO'} 351 | 352 | @classmethod 353 | def poll(cls, context): 354 | obj = context.active_object 355 | return obj is not None and obj.type == 'MESH' 356 | 357 | def execute(self, context): 358 | main_draw(context) 359 | return {'FINISHED'} 360 | 361 | 362 | 363 | 364 | 365 | class ApplyDoorway(bpy.types.Operator): 366 | bl_idname = "doorway.apply" 367 | bl_label = "Doorway apply" 368 | bl_options = {'UNDO'} 369 | 370 | def execute(self, context): 371 | lt = bpy.context.window_manager.doorway_manager 372 | cube_name = lt.cube_str_name 373 | if cube_name in bpy.context.scene.objects: 374 | cube = bpy.context.scene.objects[cube_name] 375 | obj = bpy.context.scene.objects[lt.obj_str_name] 376 | 377 | bpy.ops.object.mode_set(mode='OBJECT') 378 | bpy.context.scene.objects.active = obj 379 | bpy.ops.object.select_pattern(pattern=obj.name, case_sensitive=False, extend=False) 380 | bpy.ops.object.modifier_apply(apply_as="DATA", modifier=obj.modifiers[-1].name) 381 | 382 | bpy.ops.object.hide_view_clear() 383 | bpy.context.scene.objects.active = cube 384 | bpy.ops.object.select_pattern(pattern=cube.name, case_sensitive=False, extend=False) 385 | bpy.ops.object.delete() 386 | 387 | bpy.context.scene.objects.active = obj 388 | bpy.ops.object.select_pattern(pattern=obj.name, case_sensitive=False, extend=False) 389 | 390 | doorway_managerProps.mods_bool_count = 100 391 | doorway_managerProps.drew_it = False 392 | lt.delta_width = 0.0 393 | lt.delta_height = 0.0 394 | 395 | return {'FINISHED'} 396 | 397 | 398 | 399 | 400 | 401 | 402 | class DoorwayToolPanel(bpy.types.Panel): 403 | """Creates a Panel in the Object properties window""" 404 | bl_label = "Doorway" 405 | bl_idname = "OBJECT_PT_doorway" 406 | bl_space_type = 'VIEW_3D' 407 | bl_region_type = 'TOOLS' 408 | bl_options = {'DEFAULT_CLOSED'} 409 | 410 | @classmethod 411 | def poll(cls, context): 412 | obj = context.active_object 413 | return obj is not None and obj.type == 'MESH' 414 | 415 | def draw(self, context): 416 | layout = self.layout 417 | col = layout.column(align=True) 418 | lt = bpy.context.window_manager.doorway_manager 419 | 420 | split = col.split(percentage=0.15) 421 | if lt.display_dw: 422 | split.prop(lt, "display_dw", text="", icon='DOWNARROW_HLT') 423 | else: 424 | split.prop(lt, "display_dw", text="", icon='RIGHTARROW') 425 | 426 | if lt.mods_bool_count>90: 427 | split.operator("doorway.doorway", text = 'Create doorway') 428 | 429 | if lt.display_dw: 430 | box = col.column(align=True).box().column() 431 | col_top = box.column(align=True) 432 | row = col_top.row(align=True) 433 | if lt.mods_bool_count>90: 434 | row.label('Size:', icon = 'FULLSCREEN_ENTER') 435 | row = col_top.row(align=True) 436 | row.prop(lt, 'size_width', text = 'Width') 437 | row = col_top.row(align=True) 438 | row.prop(lt, 'size_height', text = 'Height') 439 | row = col_top.row(align=True) 440 | row.prop(lt, 'check_parallel', text = 'Only parallel planes') 441 | 442 | if lt.mods_bool_count<90: 443 | row.label('Position:', icon = 'MANIPUL') 444 | row = col_top.row(align=True) 445 | row.prop(lt, 'delta_width', text = 'Width offset') 446 | row = col_top.row(align=True) 447 | row.prop(lt, 'delta_height', text = 'Hieght offset') 448 | row = col_top.row(align=True) 449 | 450 | if lt.mods_bool_count<90: 451 | split.operator("doorway.apply", text = 'Apply') 452 | 453 | 454 | 455 | class doorway_managerProps(bpy.types.PropertyGroup): 456 | """ 457 | Fake module like class 458 | bpy.context.window_manager.doorway_manager 459 | """ 460 | size_width = bpy.props.FloatProperty(name = 'size_width') 461 | size_height = bpy.props.FloatProperty(name = 'size_height') 462 | delta_width = bpy.props.FloatProperty(name = 'delta_width', update=update_lt) 463 | delta_height = bpy.props.FloatProperty(name = 'delta_height', update=update_lt) 464 | 465 | pol_idx = bpy.props.IntProperty(name = 'pol_idx', default = -1) 466 | drew_it = bpy.props.BoolProperty(name = 'drew_it', default = False) 467 | check_parallel = bpy.props.BoolProperty(name = 'check_parallel', default = True) 468 | 469 | mods_bool_count = bpy.props.IntProperty(name = 'mods_bool_count', default = 100) 470 | cube_str_name = bpy.props.StringProperty(name = 'cube_str_name', default = 'Cube') 471 | obj_str_name = bpy.props.StringProperty(name = 'obj_str_name', default = 'Object') 472 | matrix_x0 = bpy.props.FloatProperty(name = 'matrix_x0') 473 | matrix_y0 = bpy.props.FloatProperty(name = 'matrix_y0') 474 | 475 | proem_trans = bpy.props.FloatProperty(name = 'proem_trans') 476 | 477 | display_dw = bpy.props.BoolProperty(name = "Doorway settings", 478 | description = "Display settings of the Doorway tool", 479 | default = False) 480 | 481 | 482 | 483 | 484 | class MatrixSettingItem(bpy.types.PropertyGroup): 485 | value = bpy.props.FloatProperty(name="value", default=0.0) 486 | 487 | 488 | 489 | 490 | 491 | 492 | class MessageOperator(bpy.types.Operator): 493 | bl_idname = "error.message" 494 | bl_label = "Message" 495 | type = StringProperty() 496 | message = StringProperty() 497 | 498 | def execute(self, context): 499 | self.report({'INFO'}, self.message) 500 | print(self.message) 501 | return {'FINISHED'} 502 | 503 | def invoke(self, context, event): 504 | wm = context.window_manager 505 | return wm.invoke_popup(self, width=400, height=200) 506 | 507 | def draw(self, context): 508 | self.layout.label(self.message, icon='BLENDER') 509 | 510 | 511 | def print_error(message): 512 | bpy.ops.error.message('INVOKE_DEFAULT', 513 | type = "Message", 514 | message = message) 515 | 516 | def register(): 517 | bpy.utils.register_class(MessageOperator) 518 | 519 | 520 | def unregister(): 521 | bpy.utils.unregister_class(MessageOperator) 522 | 523 | 524 | 525 | 526 | 527 | # define classes for registration 528 | classes = [CreateDoorway, DoorwayToolPanel, ApplyDoorway, \ 529 | doorway_managerProps, MatrixSettingItem] 530 | 531 | 532 | # registering and menu integration 533 | def register(): 534 | for c in classes: 535 | bpy.utils.register_class(c) 536 | bpy.types.WindowManager.doorway_manager = \ 537 | bpy.props.PointerProperty(type = doorway_managerProps) 538 | doorway_managerProps.mods_bool_count = 100 539 | doorway_managerProps.drew_it = False 540 | doorway_managerProps.pol_idx = -1 541 | 542 | bpy.types.WindowManager.doorway_matrix = \ 543 | bpy.props.CollectionProperty(type=MatrixSettingItem) 544 | 545 | 546 | # unregistering and removing menus 547 | def unregister(): 548 | del bpy.types.WindowManager.doorway_matrix 549 | del bpy.types.WindowManager.doorway_manager 550 | for c in reversed(classes): 551 | bpy.utils.unregister_class(c) 552 | 553 | if __name__ == "__main__": 554 | register() --------------------------------------------------------------------------------