diff options
134 files changed, 10157 insertions, 4218 deletions
diff --git a/add_advanced_objects_menu/__init__.py b/add_advanced_objects_menu/__init__.py index 46a3dffc..42a33445 100644 --- a/add_advanced_objects_menu/__init__.py +++ b/add_advanced_objects_menu/__init__.py @@ -25,7 +25,7 @@ bl_info = { "name": "Add Advanced Objects", "author": "Meta Androcto", - "version": (0, 1, 5), + "version": (0, 1, 6), "blender": (2, 78, 0), "location": "View3D > Add ", "description": "Add Object & Camera extras", @@ -55,7 +55,6 @@ if "bpy" in locals(): importlib.reload(arrange_on_curve) importlib.reload(mesh_easylattice) - else: from . import add_light_template from . import scene_objects_bi @@ -78,18 +77,18 @@ else: import bpy from bpy.types import ( - Menu, - AddonPreferences, - PropertyGroup, - ) + AddonPreferences, + Menu, + PropertyGroup, +) from bpy.props import ( - BoolProperty, - EnumProperty, - FloatProperty, - IntProperty, - StringProperty, - PointerProperty, - ) + BoolProperty, + EnumProperty, + FloatProperty, + IntProperty, + StringProperty, + PointerProperty, +) # Define the "Scenes" menu @@ -204,15 +203,15 @@ class AdvancedObjPreferences(AddonPreferences): bl_idname = __name__ show_menu_list = BoolProperty( - name="Menu List", - description="Show/Hide the Add Menu items", - default=False - ) + name="Menu List", + description="Show/Hide the Add Menu items", + default=False + ) show_panel_list = BoolProperty( - name="Panels List", - description="Show/Hide the Panel items", - default=False - ) + name="Panels List", + description="Show/Hide the Panel items", + default=False + ) def draw(self, context): layout = self.layout @@ -315,214 +314,214 @@ class AdvancedObjProperties(PropertyGroup): # cubester # main properties cubester_check_audio = BoolProperty( - name="", - default=False - ) + name="", + default=False + ) cubester_audio_image = EnumProperty( - name="Input Type", - items=(("image", "Image", - "Use an Image as input for generating Geometry", "IMAGE_COL", 0), - ("audio", "Audio", - "Use a Sound Strip as input for generating Geometry", "FILE_SOUND", 1)) - ) + name="Input Type", + items=(("image", "Image", + "Use an Image as input for generating Geometry", "IMAGE_COL", 0), + ("audio", "Audio", + "Use a Sound Strip as input for generating Geometry", "FILE_SOUND", 1)) + ) cubester_audio_file_length = IntProperty( - default=0 - ) + default=0 + ) # audio cubester_audio_path = StringProperty( - default="", - name="Audio File", - subtype="FILE_PATH", - update=find_audio_length - ) + default="", + name="Audio File", + subtype="FILE_PATH", + update=find_audio_length + ) cubester_audio_min_freq = IntProperty( - name="Minimum Frequency", - min=20, max=100000, - default=20 - ) + name="Minimum Frequency", + min=20, max=100000, + default=20 + ) cubester_audio_max_freq = IntProperty( - name="Maximum Frequency", - min=21, max=999999, - default=5000 - ) + name="Maximum Frequency", + min=21, max=999999, + default=5000 + ) cubester_audio_offset_type = EnumProperty( - name="Offset Type", - items=(("freq", "Frequency Offset", ""), - ("frame", "Frame Offset", "")), - description="Type of offset per row of mesh" - ) + name="Offset Type", + items=(("freq", "Frequency Offset", ""), + ("frame", "Frame Offset", "")), + description="Type of offset per row of mesh" + ) cubester_audio_frame_offset = IntProperty( - name="Frame Offset", - min=0, max=10, - default=2 - ) + name="Frame Offset", + min=0, max=10, + default=2 + ) cubester_audio_block_layout = EnumProperty( - name="Block Layout", - items=(("rectangle", "Rectangular", ""), - ("radial", "Radial", "")) - ) + name="Block Layout", + items=(("rectangle", "Rectangular", ""), + ("radial", "Radial", "")) + ) cubester_audio_width_blocks = IntProperty( - name="Width Block Count", - min=1, max=10000, - default=5 - ) + name="Width Block Count", + min=1, max=10000, + default=5 + ) cubester_audio_length_blocks = IntProperty( - name="Length Block Count", - min=1, max=10000, - default=50 - ) + name="Length Block Count", + min=1, max=10000, + default=50 + ) # image cubester_load_type = EnumProperty( - name="Image Input Type", - items=(("single", "Single Image", ""), - ("multiple", "Image Sequence", "")) - ) + name="Image Input Type", + items=(("single", "Single Image", ""), + ("multiple", "Image Sequence", "")) + ) cubester_image = StringProperty( - default="", - name="" - ) + default="", + name="" + ) cubester_load_image = StringProperty( - default="", - name="Load Image", - subtype="FILE_PATH", - update=adjust_selected_image - ) + default="", + name="Load Image", + subtype="FILE_PATH", + update=adjust_selected_image + ) cubester_skip_images = IntProperty( - name="Image Step", - min=1, max=30, - default=1, - description="Step from image to image by this number" - ) + name="Image Step", + min=1, max=30, + default=1, + description="Step from image to image by this number" + ) cubester_max_images = IntProperty( - name="Max Number Of Images", - min=2, max=1000, - default=10, - description="Maximum number of images to be used" - ) + name="Max Number Of Images", + min=2, max=1000, + default=10, + description="Maximum number of images to be used" + ) cubester_frame_step = IntProperty( - name="Frame Step Size", - min=1, max=10, - default=4, - description="The number of frames each picture is used" - ) + name="Frame Step Size", + min=1, max=10, + default=4, + description="The number of frames each picture is used" + ) cubester_skip_pixels = IntProperty( - name="Skip # Pixels", - min=0, max=256, - default=64, - description="Skip this number of pixels before placing the next" - ) + name="Skip # Pixels", + min=0, max=256, + default=64, + description="Skip this number of pixels before placing the next" + ) cubester_mesh_style = EnumProperty( - name="Mesh Type", - items=(("blocks", "Blocks", ""), - ("plane", "Plane", "")), - description="Compose mesh of multiple blocks or of a single plane" - ) + name="Mesh Type", + items=(("blocks", "Blocks", ""), + ("plane", "Plane", "")), + description="Compose mesh of multiple blocks or of a single plane" + ) cubester_block_style = EnumProperty( - name="Block Style", - items=(("size", "Vary Size", ""), - ("position", "Vary Position", "")), - description="Vary Z-size of block, or vary Z-position" - ) + name="Block Style", + items=(("size", "Vary Size", ""), + ("position", "Vary Position", "")), + description="Vary Z-size of block, or vary Z-position" + ) cubester_height_scale = FloatProperty( - name="Height Scale", - subtype="DISTANCE", - min=0.1, max=2, - default=0.2 - ) + name="Height Scale", + subtype="DISTANCE", + min=0.1, max=2, + default=0.2 + ) cubester_invert = BoolProperty( - name="Invert Height", - default=False - ) + name="Invert Height", + default=False + ) # general adjustments cubester_size_per_hundred_pixels = FloatProperty( - name="Size Per 100 Blocks/Points", - subtype="DISTANCE", - min=0.001, max=5, - default=1 - ) + name="Size Per 100 Blocks/Points", + subtype="DISTANCE", + min=0.001, max=5, + default=1 + ) # material based stuff cubester_materials = EnumProperty( - name="Material", - items=(("vertex", "Vertex Colors", ""), - ("image", "Image", "")), - description="Color with vertex colors, or uv unwrap and use an image" - ) + name="Material", + items=(("vertex", "Vertex Colors", ""), + ("image", "Image", "")), + description="Color with vertex colors, or uv unwrap and use an image" + ) cubester_use_image_color = BoolProperty( - name="Use Original Image Colors'?", - default=True, - description="Use original image colors, or replace with an another one" - ) + name="Use Original Image Colors'?", + default=True, + description="Use original image colors, or replace with an another one" + ) cubester_color_image = StringProperty( - default="", - name="" - ) + default="", + name="" + ) cubester_load_color_image = StringProperty( - default="", - name="Load Color Image", - subtype="FILE_PATH", - update=adjust_selected_color_image - ) + default="", + name="Load Color Image", + subtype="FILE_PATH", + update=adjust_selected_color_image + ) cubester_vertex_colors = {} # advanced cubester_advanced = BoolProperty( - name="Advanced Options", - default=False - ) + name="Advanced Options", + default=False + ) cubester_random_weights = BoolProperty( - name="Random Weights", - default=False - ) + name="Random Weights", + default=False + ) cubester_weight_r = FloatProperty( - name="Red", - subtype="FACTOR", - min=0.01, max=1.0, - default=0.25 - ) + name="Red", + subtype="FACTOR", + min=0.01, max=1.0, + default=0.25 + ) cubester_weight_g = FloatProperty( - name="Green", - subtype="FACTOR", - min=0.01, max=1.0, - default=0.25 - ) + name="Green", + subtype="FACTOR", + min=0.01, max=1.0, + default=0.25 + ) cubester_weight_b = FloatProperty( - name="Blue", - subtype="FACTOR", - min=0.01, max=1.0, - default=0.25 - ) + name="Blue", + subtype="FACTOR", + min=0.01, max=1.0, + default=0.25 + ) cubester_weight_a = FloatProperty( - name="Alpha", - subtype="FACTOR", - min=0.01, max=1.0, - default=0.25 - ) + name="Alpha", + subtype="FACTOR", + min=0.01, max=1.0, + default=0.25 + ) # arrange_on_curve arrange_c_use_selected = BoolProperty( - name="Use Selected", - description="Use the selected objects to duplicate", - default=True, - ) + name="Use Selected", + description="Use the selected objects to duplicate", + default=True, + ) arrange_c_obj_arranjar = StringProperty( - name="" - ) + name="" + ) arrange_c_select_type = EnumProperty( - name="Type", - description="Select object or group", - items=[ - ('O', "Object", "Make duplicates of a specific object"), - ('G', "Group", "Make duplicates of the objects in a group"), - ], - default='O', - ) + name="Type", + description="Select object or group", + items=[ + ('O', "Object", "Make duplicates of a specific object"), + ('G', "Group", "Make duplicates of the objects in a group"), + ], + default='O', + ) def register(): bpy.utils.register_module(__name__) bpy.types.Scene.advanced_objects = PointerProperty( - type=AdvancedObjProperties - ) + type=AdvancedObjProperties + ) # Add "Extras" menu to the "Add" menu bpy.types.INFO_MT_add.append(menu) @@ -543,10 +542,6 @@ def unregister(): bpy.utils.unregister_module(__name__) del bpy.types.Scene.advanced_objects - # cleanup Easy Lattice Scene Property if it was created - if hasattr(bpy.types.Scene, "activelatticeobject"): - del bpy.types.Scene.activelatticeobject - if __name__ == "__main__": register() diff --git a/add_advanced_objects_menu/cubester.py b/add_advanced_objects_menu/cubester.py index f1a8f581..87322f4f 100644 --- a/add_advanced_objects_menu/cubester.py +++ b/add_advanced_objects_menu/cubester.py @@ -25,28 +25,28 @@ bl_info = { "name": "CubeSter", "author": "Jacob Morris", - "version": (0, 7, 1), + "version": (0, 7, 2), "blender": (2, 78, 0), "location": "View 3D > Toolbar > CubeSter", "description": "Takes image, image sequence, or audio file and converts it " "into a height map based on pixel color and alpha values", "category": "Add Mesh" - } +} import bpy import bmesh from bpy.types import ( - Operator, - Panel, - ) + Operator, + Panel, +) import timeit from random import uniform from math import radians from os import ( - path, - listdir, - ) + path, + listdir, +) # create block at center position x, y with block width 2 * hx and 2 * hy and height of h @@ -146,18 +146,18 @@ def create_material(scene, ob, name): if adv_obj.cubester_materials == "image": mat.node_tree.links.new( - nodes["Image Texture"].outputs[0], - nodes["Diffuse BSDF"].inputs[0] - ) + nodes["Image Texture"].outputs[0], + nodes["Diffuse BSDF"].inputs[0] + ) mat.node_tree.links.new( - nodes["Texture Coordinate"].outputs[2], - nodes["Image Texture"].inputs[0] - ) + nodes["Texture Coordinate"].outputs[2], + nodes["Image Texture"].inputs[0] + ) else: mat.node_tree.links.new( - nodes["Attribute"].outputs[0], - nodes["Diffuse BSDF"].inputs[0] - ) + nodes["Attribute"].outputs[0], + nodes["Diffuse BSDF"].inputs[0] + ) else: if adv_obj.cubester_materials == "image" or scene.render.engine != "BLENDER_RENDER": tex = bpy.data.textures.new("CubeSter_" + name, "IMAGE") @@ -177,9 +177,11 @@ def create_mesh_from_audio(self, scene, verts, faces): audio_filepath = adv_obj.cubester_audio_path width = adv_obj.cubester_audio_width_blocks length = adv_obj.cubester_audio_length_blocks - size_per_hundred = adv_obj.cubester_size_per_hundred_pixels + size_per_hundred = adv_obj.cubester_size_per_hundred_pixels size = size_per_hundred / 100 + # Note: used for compatibility with vertex colors changes + bl_version = bool(bpy.app.version >= (2, 79, 1)) # create all blocks y = -(width / 2) * size + (size / 2) @@ -212,7 +214,8 @@ def create_mesh_from_audio(self, scene, verts, faces): # go through each column, step by appropriate amount for column in range(0, picture.size[0] * 4, 4 + skip_x * 4): r, g, b, a = get_pixel_values(picture, pixels, row, column) - vert_colors += [(r, g, b) for i in range(24)] + get_colors = (r, g, b, a) if bl_version else (r, g, b) + vert_colors += [get_colors for i in range(24)] bpy.ops.mesh.vertex_color_add() @@ -244,7 +247,8 @@ def create_mesh_from_audio(self, scene, verts, faces): for row in range(0, picture.size[1], skip_y + 1): for column in range(0, picture.size[0] * 4, 4 + skip_x * 4): r, g, b, a = get_pixel_values(picture, pixels, row, column) - frame_colors += [(r, g, b) for i in range(24)] + get_colors = (r, g, b, a) if bl_version else (r, g, b) + frame_colors += [get_colors for i in range(24)] frames_vert_colors.append(frame_colors) @@ -354,6 +358,8 @@ def create_mesh_from_image(self, scene, verts, faces): adv_obj = scene.advanced_objects picture = bpy.data.images[adv_obj.cubester_image] pixels = list(picture.pixels) + # Note: used for compatibility with vertex colors changes + bl_version = bool(bpy.app.version >= (2, 79, 1)) x_pixels = picture.size[0] / (adv_obj.cubester_skip_pixels + 1) y_pixels = picture.size[1] / (adv_obj.cubester_skip_pixels + 1) @@ -367,7 +373,6 @@ def create_mesh_from_image(self, scene, verts, faces): y = -height / 2 + half_width vert_colors = [] - weights = [uniform(0.0, 1.0) for i in range(4)] # random weights rows = 0 # go through each row of pixels stepping by adv_obj.cubester_skip_pixels + 1 @@ -377,16 +382,17 @@ def create_mesh_from_image(self, scene, verts, faces): # go through each column, step by appropriate amount for column in range(0, picture.size[0] * 4, 4 + adv_obj.cubester_skip_pixels * 4): r, g, b, a = get_pixel_values(picture, pixels, row, column) + get_colors = (r, g, b, a) if bl_version else (r, g, b) h = find_point_height(r, g, b, a, scene) # if not transparent if h != -1: if adv_obj.cubester_mesh_style == "blocks": create_block(x, y, half_width, h, verts, faces) - vert_colors += [(r, g, b) for i in range(24)] + vert_colors += [get_colors for i in range(24)] else: verts += [(x, y, h)] - vert_colors += [(r, g, b) for i in range(4)] + vert_colors += [get_colors for i in range(4)] x += step y += step @@ -464,14 +470,15 @@ def create_mesh_from_image(self, scene, verts, faces): for row in range(0, picture.size[1], adv_obj.cubester_skip_pixels + 1): for column in range(0, picture.size[0] * 4, 4 + adv_obj.cubester_skip_pixels * 4): r, g, b, a = get_pixel_values(picture, pixels, row, column) + get_colors = (r, g, b, a) if bl_version else (r, g, b) h = find_point_height(r, g, b, a, scene) if h != -1: frame_heights.append(h) if adv_obj.cubester_mesh_style == "blocks": - frame_colors += [(r, g, b) for i in range(24)] + frame_colors += [get_colors for i in range(24)] else: - frame_colors += [(r, g, b) for i in range(4)] + frame_colors += [get_colors for i in range(4)] if adv_obj.cubester_mesh_style == "plane": del vert_colors[len(vert_colors) - 4:len(vert_colors)] @@ -482,24 +489,24 @@ def create_mesh_from_image(self, scene, verts, faces): # determine what data to use if adv_obj.cubester_materials == "vertex" or scene.render.engine == "BLENDER_ENGINE": adv_obj.cubester_vertex_colors[ob.name] = { - "type": "vertex", "frames": frames_vert_colors, - "frame_skip": adv_obj.cubester_frame_step, - "total_images": max_images - } + "type": "vertex", "frames": frames_vert_colors, + "frame_skip": adv_obj.cubester_frame_step, + "total_images": max_images + } else: adv_obj.cubester_vertex_colors[ob.name] = { - "type": "image", "frame_skip": scene.cubester_frame_step, - "total_images": max_images - } + "type": "image", "frame_skip": adv_obj.cubester_frame_step, + "total_images": max_images + } att = get_image_node(ob.data.materials[0]) att.image_user.frame_duration = len(frames) * adv_obj.cubester_frame_step # animate mesh create_f_curves( - mesh, frames, - adv_obj.cubester_frame_step, - adv_obj.cubester_mesh_style - ) + mesh, frames, + adv_obj.cubester_frame_step, + adv_obj.cubester_mesh_style + ) # generate uv map for object @@ -879,11 +886,12 @@ class CubeSterPanel(Panel): class CubeSter(Operator): bl_idname = "mesh.cubester" - bl_label = "Generate Mesh" + bl_label = "Generate CubeSter Mesh" bl_description = "Generate a mesh from an Image or Sound File" bl_options = {"REGISTER", "UNDO"} def execute(self, context): + verts, faces = [], [] start = timeit.default_timer() @@ -901,7 +909,8 @@ class CubeSter(Operator): return {"CANCELLED"} else: if (adv_obj.cubester_audio_path != "" and - path.isfile(adv_obj.cubester_audio_path) and adv_obj.cubester_check_audio is True): + path.isfile(adv_obj.cubester_audio_path) and + adv_obj.cubester_check_audio is True): create_mesh_from_audio(self, scene, verts, faces) created = adv_obj.cubester_audio_file_length @@ -913,19 +922,21 @@ class CubeSter(Operator): stop = timeit.default_timer() if adv_obj.cubester_mesh_style == "blocks" or adv_obj.cubester_audio_image == "audio": - self.report({"INFO"}, - "CubeSter: {} blocks and {} frame(s) " - "in {}s".format(str(int(len(verts) / 8)), - str(created), - str(round(stop - start, 4))) - ) + self.report( + {"INFO"}, + "CubeSter: {} blocks and {} frame(s) " + "in {}s".format(str(int(len(verts) / 8)), + str(created), + str(round(stop - start, 4))) + ) else: - self.report({"INFO"}, - "CubeSter: {} points and {} frame(s) " - "in {}s" .format(str(len(verts)), - str(created), - str(round(stop - start, 4))) - ) + self.report( + {"INFO"}, + "CubeSter: {} points and {} frame(s) " + "in {}s" .format(str(len(verts)), + str(created), + str(round(stop - start, 4))) + ) return {"FINISHED"} diff --git a/add_curve_ivygen.py b/add_curve_ivygen.py index dd523102..7a90b3b9 100644 --- a/add_curve_ivygen.py +++ b/add_curve_ivygen.py @@ -21,9 +21,9 @@ bl_info = { "name": "IvyGen", "author": "testscreenings, PKHG, TrumanBlending", - "version": (0, 1, 2), + "version": (0, 1, 4), "blender": (2, 59, 0), - "location": "View3D > Add > Curve", + "location": "View3D > Tool Shelf > Create > Ivy Generator", "description": "Adds generated ivy to a mesh object starting " "at the 3D cursor", "warning": "", @@ -34,10 +34,16 @@ bl_info = { import bpy +from bpy.types import ( + Operator, + Panel, + PropertyGroup, + ) from bpy.props import ( + BoolProperty, FloatProperty, IntProperty, - BoolProperty, + PointerProperty, ) from mathutils import ( Vector, @@ -191,7 +197,7 @@ def createIvyGeometry(IVY, growLeaves): if growLeaves: faceList = [[4 * i + l for l in range(4)] for i in - range(len(vertList) // 4)] + range(len(vertList) // 4)] # Generate the new leaf mesh and link me = bpy.data.meshes.new('IvyLeaf') @@ -212,24 +218,6 @@ def createIvyGeometry(IVY, growLeaves): ob.parent = newCurve -''' -def computeBoundingSphere(ob): - # Get the mesh data - me = ob.data - # Intialise the center - center = Vector((0.0, 0.0, 0.0)) - # Add all vertex coords - for v in me.vertices: - center += v.co - # Average over all verts - center /= len(me.vertices) - # Create the iterator and find its max - length_iter = ((center - v.co).length for v in me.vertices) - radius = max(length_iter) - return radius -''' - - class IvyNode: """ The basic class used for each point on the ivy which is grown.""" __slots__ = ('pos', 'primaryDir', 'adhesionVector', 'adhesionLength', @@ -463,12 +451,204 @@ def check_mesh_faces(ob): return False -class IvyGen(bpy.types.Operator): +class IvyGen(Operator): bl_idname = "curve.ivy_gen" bl_label = "IvyGen" bl_description = "Generate Ivy on an Mesh Object" bl_options = {'REGISTER', 'UNDO'} + updateIvy = BoolProperty( + name="Update Ivy", + description="Update the Ivy location based on the cursor and Panel settings", + default=False + ) + defaultIvy = BoolProperty( + name="Default Ivy", + options={"HIDDEN", "SKIP_SAVE"}, + default=False + ) + + @classmethod + def poll(self, context): + # Check if there's an object and whether it's a mesh + ob = context.active_object + return ((ob is not None) and + (ob.type == 'MESH') and + (context.mode == 'OBJECT')) + + def invoke(self, context, event): + self.updateIvy = True + return self.execute(context) + + def execute(self, context): + # scene = context.scene + ivyProps = context.window_manager.ivy_gen_props + + if not self.updateIvy: + return {'PASS_THROUGH'} + + # assign the variables, check if it is default + # Note: update the values if window_manager props defaults are changed + randomSeed = ivyProps.randomSeed if not self.defaultIvy else 0 + maxTime = ivyProps.maxTime if not self.defaultIvy else 0 + maxIvyLength = ivyProps.maxIvyLength if not self.defaultIvy else 1.0 + ivySize = ivyProps.ivySize if not self.defaultIvy else 0.02 + maxFloatLength = ivyProps.maxFloatLength if not self.defaultIvy else 0.5 + maxAdhesionDistance = ivyProps.maxAdhesionDistance if not self.defaultIvy else 1.0 + primaryWeight = ivyProps.primaryWeight if not self.defaultIvy else 0.5 + randomWeight = ivyProps.randomWeight if not self.defaultIvy else 0.2 + gravityWeight = ivyProps.gravityWeight if not self.defaultIvy else 1.0 + adhesionWeight = ivyProps.adhesionWeight if not self.defaultIvy else 0.1 + branchingProbability = ivyProps.branchingProbability if not self.defaultIvy else 0.05 + leafProbability = ivyProps.leafProbability if not self.defaultIvy else 0.35 + ivyBranchSize = ivyProps.ivyBranchSize if not self.defaultIvy else 0.001 + ivyLeafSize = ivyProps.ivyLeafSize if not self.defaultIvy else 0.02 + growLeaves = ivyProps.growLeaves if not self.defaultIvy else True + + bpy.ops.object.mode_set(mode='EDIT', toggle=False) + bpy.ops.object.mode_set(mode='OBJECT', toggle=False) + + # Get the selected object + ob = context.active_object + + # Check if the mesh has at least one polygon since some functions + # are expecting them in the object's data (see T51753) + check_face = check_mesh_faces(ob) + if check_face is False: + self.report({'WARNING'}, + "Mesh Object doesn't have at least one Face. " + "Operation Cancelled") + return {"CANCELLED"} + + # Compute bounding sphere radius + # radius = computeBoundingSphere(ob) # Not needed anymore + + # Get the seeding point + seedPoint = context.scene.cursor_location + + # Fix the random seed + rand_seed(randomSeed) + + # Make the new ivy + IVY = Ivy( + primaryWeight=primaryWeight, + randomWeight=randomWeight, + gravityWeight=gravityWeight, + adhesionWeight=adhesionWeight, + branchingProbability=branchingProbability, + leafProbability=leafProbability, + ivySize=ivySize, + ivyLeafSize=ivyLeafSize, + ivyBranchSize=ivyBranchSize, + maxFloatLength=maxFloatLength, + maxAdhesionDistance=maxAdhesionDistance + ) + # Generate first root and node + IVY.seed(seedPoint) + + checkTime = False + maxLength = maxIvyLength # * radius + + # If we need to check time set the flag + if maxTime != 0.0: + checkTime = True + + t = time.time() + startPercent = 0.0 + checkAliveIter = [True, ] + + # Grow until 200 roots is reached or backup counter exceeds limit + while (any(checkAliveIter) and + (IVY.maxLength < maxLength) and + (not checkTime or (time.time() - t < maxTime))): + # Grow the ivy for this iteration + IVY.grow(ob) + + # Print the proportion of ivy growth to console + if (IVY.maxLength / maxLength * 100) > 10 * startPercent // 10: + print('%0.2f%% of Ivy nodes have grown' % + (IVY.maxLength / maxLength * 100)) + startPercent += 10 + if IVY.maxLength / maxLength > 1: + print("Halting Growth") + + # Make an iterator to check if all are alive + checkAliveIter = (r.alive for r in IVY.ivyRoots) + + # Create the curve and leaf geometry + createIvyGeometry(IVY, growLeaves) + print("Geometry Generation Complete") + + print("Ivy generated in %0.2f s" % (time.time() - t)) + + self.updateIvy = False + self.defaultIvy = False + + return {'FINISHED'} + + def draw(self, context): + layout = self.layout + + layout.prop(self, "updateIvy", icon="FILE_REFRESH") + + +class CURVE_PT_IvyGenPanel(Panel): + bl_label = "Ivy Generator" + bl_idname = "CURVE_PT_IvyGenPanel" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_category = "Create" + bl_context = 'objectmode' + bl_options = {"DEFAULT_CLOSED"} + + def draw(self, context): + layout = self.layout + wm = context.window_manager + col = layout.column(align=True) + + prop_new = col.operator("curve.ivy_gen", text="Add New Ivy", icon="OUTLINER_OB_CURVE") + prop_new.defaultIvy = False + prop_new.updateIvy = True + + prop_def = col.operator("curve.ivy_gen", text="Add New Default Ivy", icon="CURVE_DATA") + prop_def.defaultIvy = True + prop_def.updateIvy = True + + col = layout.column(align=True) + col.label("Generation Settings:") + col.prop(wm.ivy_gen_props, "randomSeed") + col.prop(wm.ivy_gen_props, "maxTime") + + col = layout.column(align=True) + col.label("Size Settings:") + col.prop(wm.ivy_gen_props, "maxIvyLength") + col.prop(wm.ivy_gen_props, "ivySize") + col.prop(wm.ivy_gen_props, "maxFloatLength") + col.prop(wm.ivy_gen_props, "maxAdhesionDistance") + + col = layout.column(align=True) + col.label("Weight Settings:") + col.prop(wm.ivy_gen_props, "primaryWeight") + col.prop(wm.ivy_gen_props, "randomWeight") + col.prop(wm.ivy_gen_props, "gravityWeight") + col.prop(wm.ivy_gen_props, "adhesionWeight") + + col = layout.column(align=True) + col.label("Branch Settings:") + col.prop(wm.ivy_gen_props, "branchingProbability") + col.prop(wm.ivy_gen_props, "ivyBranchSize") + + col = layout.column(align=True) + col.prop(wm.ivy_gen_props, "growLeaves") + + if wm.ivy_gen_props.growLeaves: + col = layout.column(align=True) + col.label("Leaf Settings:") + col.prop(wm.ivy_gen_props, "ivyLeafSize") + col.prop(wm.ivy_gen_props, "leafProbability") + + +class IvyGenProperties(PropertyGroup): maxIvyLength = FloatProperty( name="Max Ivy Length", description="Maximum ivy length in Blender Units", @@ -551,7 +731,8 @@ class IvyGen(bpy.types.Operator): "can live while floating", default=0.5, min=0.0, - soft_max=1.0) + soft_max=1.0 + ) maxAdhesionDistance = FloatProperty( name="Max Adhesion Length", description="The maximum distance that a branch " @@ -580,168 +761,29 @@ class IvyGen(bpy.types.Operator): description="Grow leaves or not", default=True ) - updateIvy = BoolProperty( - name="Update Ivy", - default=False - ) - - @classmethod - def poll(self, context): - # Check if there's an object and whether it's a mesh - ob = context.active_object - return ((ob is not None) and - (ob.type == 'MESH') and - (context.mode == 'OBJECT')) - - def invoke(self, context, event): - self.updateIvy = True - return self.execute(context) - - def execute(self, context): - if not self.updateIvy: - return {'PASS_THROUGH'} - - bpy.ops.object.mode_set(mode='EDIT', toggle=False) - bpy.ops.object.mode_set(mode='OBJECT', toggle=False) - - # Get the selected object - ob = context.active_object - - # Check if the mesh has at least one polygon since some functions - # are expecting them in the object's data (see T51753) - check_face = check_mesh_faces(ob) - if check_face is False: - self.report({'WARNING'}, - "Mesh Object doesn't have at least one Face. " - "Operation Cancelled") - return {"CANCELLED"} - - # Compute bounding sphere radius - # radius = computeBoundingSphere(ob) # Not needed anymore - - # Get the seeding point - seedPoint = context.scene.cursor_location - - # Fix the random seed - rand_seed(self.randomSeed) - - # Make the new ivy - IVY = Ivy(**self.as_keywords(ignore=('randomSeed', 'growLeaves', - 'maxIvyLength', 'maxTime', 'updateIvy'))) - - # Generate first root and node - IVY.seed(seedPoint) - - checkTime = False - maxLength = self.maxIvyLength # * radius - - # If we need to check time set the flag - if self.maxTime != 0.0: - checkTime = True - t = time.time() - startPercent = 0.0 - checkAliveIter = [True, ] - - # Grow until 200 roots is reached or backup counter exceeds limit - while (any(checkAliveIter) and - (IVY.maxLength < maxLength) and - (not checkTime or (time.time() - t < self.maxTime))): - # Grow the ivy for this iteration - IVY.grow(ob) - # Print the proportion of ivy growth to console - if (IVY.maxLength / maxLength * 100) > 10 * startPercent // 10: - print('%0.2f%% of Ivy nodes have grown' % - (IVY.maxLength / maxLength * 100)) - startPercent += 10 - if IVY.maxLength / maxLength > 1: - print("Halting Growth") - - # Make an iterator to check if all are alive - checkAliveIter = (r.alive for r in IVY.ivyRoots) - - # Create the curve and leaf geometry - createIvyGeometry(IVY, self.growLeaves) - print("Geometry Generation Complete") - - print("Ivy generated in %0.2f s" % (time.time() - t)) - - self.updateIvy = False - - return {'FINISHED'} - - def draw(self, context): - layout = self.layout - - layout.prop(self, 'updateIvy', icon='CURVE_DATA') - - properties = layout.operator('curve.ivy_gen', text="Add New Ivy") - properties.randomSeed = self.randomSeed - properties.maxTime = self.maxTime - properties.maxIvyLength = self.maxIvyLength - properties.ivySize = self.ivySize - properties.maxFloatLength = self.maxFloatLength - properties.maxAdhesionDistance = self.maxAdhesionDistance - properties.primaryWeight = self.primaryWeight - properties.randomWeight = self.randomWeight - properties.gravityWeight = self.gravityWeight - properties.adhesionWeight = self.adhesionWeight - properties.branchingProbability = self.branchingProbability - properties.leafProbability = self.leafProbability - properties.ivyBranchSize = self.ivyBranchSize - properties.ivyLeafSize = self.ivyLeafSize - properties.updateIvy = True - - prop_def = layout.operator('curve.ivy_gen', text="Add New Default Ivy") - prop_def.updateIvy = True - - layout.prop(self, 'growLeaves') - - box = layout.box() - box.label("Generation Settings:") - box.prop(self, 'randomSeed') - box.prop(self, 'maxTime') - - box = layout.box() - box.label("Size Settings:") - box.prop(self, 'maxIvyLength') - box.prop(self, 'ivySize') - box.prop(self, 'maxFloatLength') - box.prop(self, 'maxAdhesionDistance') - - box = layout.box() - box.label("Weight Settings:") - box.prop(self, 'primaryWeight') - box.prop(self, 'randomWeight') - box.prop(self, 'gravityWeight') - box.prop(self, 'adhesionWeight') - - box = layout.box() - box.label("Branch Settings:") - box.prop(self, 'branchingProbability') - box.prop(self, 'ivyBranchSize') - - if self.growLeaves: - box = layout.box() - box.label("Leaf Settings:") - box.prop(self, 'ivyLeafSize') - box.prop(self, 'leafProbability') - - -def menu_func(self, context): - self.layout.operator(IvyGen.bl_idname, text="Add Ivy to Mesh", - icon='OUTLINER_DATA_CURVE').updateIvy = True +classes = ( + IvyGen, + IvyGenProperties, + CURVE_PT_IvyGenPanel +) def register(): - bpy.utils.register_module(__name__) - bpy.types.INFO_MT_curve_add.append(menu_func) + for cls in classes: + bpy.utils.register_class(cls) + + bpy.types.WindowManager.ivy_gen_props = PointerProperty( + type=IvyGenProperties + ) def unregister(): - bpy.types.INFO_MT_curve_add.remove(menu_func) - bpy.utils.unregister_module(__name__) + del bpy.types.WindowManager.ivy_gen_props + + for cls in reversed(classes): + bpy.utils.unregister_class(cls) if __name__ == "__main__": diff --git a/ant_landscape/add_mesh_ant_landscape.py b/ant_landscape/add_mesh_ant_landscape.py index 5d25cf24..6a36c42f 100644 --- a/ant_landscape/add_mesh_ant_landscape.py +++ b/ant_landscape/add_mesh_ant_landscape.py @@ -586,6 +586,14 @@ class AntAddLandscape(bpy.types.Operator): description="Automatic refresh" ) + @classmethod + def poll(self, context): + ob = context.object + if ob is not None: + if ob.mode == 'EDIT': + return False + return True + def draw(self, context): draw_ant_refresh(self, context) draw_ant_main(self, context, generate=True) diff --git a/ant_landscape/ant_functions.py b/ant_landscape/ant_functions.py index 3b515933..3c0f2c34 100644 --- a/ant_landscape/ant_functions.py +++ b/ant_landscape/ant_functions.py @@ -193,7 +193,10 @@ class AntLandscapeRegenerate(bpy.types.Operator): @classmethod def poll(cls, context): - return bpy.context.active_object.ant_landscape + ob = bpy.context.active_object + if ob.mode == 'EDIT': + return False + return ob.ant_landscape def execute(self, context): diff --git a/archipack/archipack_material.py b/archipack/archipack_material.py index c226c7fb..22da496d 100644 --- a/archipack/archipack_material.py +++ b/archipack/archipack_material.py @@ -223,6 +223,8 @@ class MaterialSetManager(): Store sets for each object type """ self.objects = {} + # hold reference of dynamic enumerator + self.enums = {} def get_filename(self, object_type): @@ -234,6 +236,7 @@ class MaterialSetManager(): def cleanup(self): self.objects.clear() + self.enums.clear() def register_set(self, object_type, set_name, materials_names): @@ -275,16 +278,20 @@ class MaterialSetManager(): finally: f.close() - for s_key in material_sets.keys(): + s_keys = material_sets.keys() + for s_key in s_keys: self.register_set(object_type, s_key, material_sets[s_key]) + self.make_enum(object_type, s_keys) + def save(self, object_type): # always save in user prefs filename = self.get_filename(object_type) # print("filename:%s" % filename) o_dict = self.objects[object_type] lines = [] - for s_key in o_dict.keys(): + s_keys = o_dict.keys() + for s_key in s_keys: for mat in o_dict[s_key]: lines.append("{}##|##{}\n".format(s_key, mat)) try: @@ -296,6 +303,8 @@ class MaterialSetManager(): finally: f.close() + self.make_enum(object_type, s_keys) + def add(self, context, set_name): o = context.active_object if "archipack_material" in o: @@ -311,9 +320,11 @@ class MaterialSetManager(): d = o.archipack_material[0] object_type = d.category set_name = d.material - if set_name in self.objects[object_type].keys(): + s_keys = self.objects[object_type].keys() + if set_name in s_keys: self.objects[object_type].pop(set_name) self.save(object_type) + self.make_enum(object_type, s_keys) def get_materials(self, object_type, set_name): if object_type not in self.objects.keys(): @@ -326,7 +337,13 @@ class MaterialSetManager(): return None return self.objects[object_type][set_name] - def make_enum(self, object_type): + def make_enum(self, object_type, s_keys): + if len(s_keys) < 1: + self.enums[object_type] = [('DEFAULT', 'Default', '', 0)] + else: + self.enums[object_type] = [(s.upper(), s.capitalize(), '', i) for i, s in enumerate(s_keys)] + + def get_enum(self, object_type): if object_type not in self.objects.keys(): self.load(object_type) @@ -334,19 +351,14 @@ class MaterialSetManager(): if object_type not in self.objects.keys(): self.objects[object_type] = {} - s_keys = self.objects[object_type].keys() - - if len(s_keys) < 1: - return [('DEFAULT', 'Default', '', 0)] - - return [(s.upper(), s.capitalize(), '', i) for i, s in enumerate(s_keys)] + return self.enums[object_type] def material_enum(self, context): global setman if setman is None: setman = MaterialSetManager() - return setman.make_enum(self.category) + return setman.get_enum(self.category) def update(self, context): diff --git a/bone_selection_sets.py b/bone_selection_sets.py index 46aa7f25..686eb808 100644 --- a/bone_selection_sets.py +++ b/bone_selection_sets.py @@ -18,8 +18,8 @@ bl_info = { "name": "Bone Selection Sets", - "author": "Inês Almeida, Antony Riakiotakis, Dan Eicher", - "version": (2, 0, 1), + "author": "Inês Almeida, Sybren A. Stüvel, Antony Riakiotakis, Dan Eicher", + "version": (2, 1, 1), "blender": (2, 75, 0), "location": "Properties > Object Data (Armature) > Selection Sets", "description": "List of Bone sets for easy selection while animating", @@ -31,18 +31,18 @@ bl_info = { import bpy from bpy.types import ( - Operator, - Menu, - Panel, - UIList, - PropertyGroup, - ) + Operator, + Menu, + Panel, + UIList, + PropertyGroup, +) from bpy.props import ( - StringProperty, - IntProperty, - EnumProperty, - CollectionProperty, - ) + StringProperty, + IntProperty, + EnumProperty, + CollectionProperty, +) # Data Structure ############################################################## @@ -68,6 +68,8 @@ class POSE_MT_selection_sets_specials(Menu): layout.operator("pose.selection_set_delete_all", icon='X') layout.operator("pose.selection_set_remove_bones", icon='X') + layout.operator("pose.selection_set_copy", icon='COPYDOWN') + layout.operator("pose.selection_set_paste", icon='PASTEDOWN') class POSE_PT_selection_sets(Panel): @@ -128,21 +130,35 @@ class POSE_UL_selection_set(UIList): layout.prop(set, "name", text="", icon='GROUP_BONE', emboss=False) -class POSE_MT_create_new_selection_set(Menu): - bl_idname = "POSE_MT_selection_set_create" +class POSE_MT_selection_set_create(Menu): bl_label = "Choose Selection Set" def draw(self, context): layout = self.layout layout.operator("pose.selection_set_add_and_assign", - text="New Selection Set") + text="New Selection Set") + + +class POSE_MT_selection_sets(Menu): + bl_label = 'Select Selection Set' + + @classmethod + def poll(cls, context): + return POSE_OT_selection_set_select.poll(context) + + def draw(self, context): + layout = self.layout + layout.operator_context = 'EXEC_DEFAULT' + for idx, sel_set in enumerate(context.object.selection_sets): + props = layout.operator(POSE_OT_selection_set_select.bl_idname, text=sel_set.name) + props.selection_set_index = idx # Operators ################################################################### class PluginOperator(Operator): @classmethod - def poll(self, context): + def poll(cls, context): return (context.object and context.object.type == 'ARMATURE' and context.mode == 'POSE') @@ -150,12 +166,11 @@ class PluginOperator(Operator): class NeedSelSetPluginOperator(PluginOperator): @classmethod - def poll(self, context): - if super().poll(context): - arm = context.object - return (arm.active_selection_set < len(arm.selection_sets) and - arm.active_selection_set >= 0) - return False + def poll(cls, context): + if not super().poll(context): + return False + arm = context.object + return 0 <= arm.active_selection_set < len(arm.selection_sets) class POSE_OT_selection_set_delete_all(PluginOperator): @@ -206,11 +221,11 @@ class POSE_OT_selection_set_move(NeedSelSetPluginOperator): ) @classmethod - def poll(self, context): - if super().poll(context): - arm = context.object - return len(arm.selection_sets) > 1 - return False + def poll(cls, context): + if not super().poll(context): + return False + arm = context.object + return len(arm.selection_sets) > 1 def execute(self, context): arm = context.object @@ -235,30 +250,12 @@ class POSE_OT_selection_set_add(PluginOperator): def execute(self, context): arm = context.object - - new_sel_set = arm.selection_sets.add() - - # naming - if "SelectionSet" not in arm.selection_sets: - new_sel_set.name = "SelectionSet" - else: - sorted_sets = [] - for selset in arm.selection_sets: - if selset.name.startswith("SelectionSet."): - index = selset.name[13:] - if index.isdigit(): - sorted_sets.append(index) - sorted_sets = sorted(sorted_sets) - min_index = 1 - for num in sorted_sets: - num = int(num) - if min_index < num: - break - min_index = num + 1 - new_sel_set.name = "SelectionSet.{:03d}".format(min_index) + sel_sets = arm.selection_sets + new_sel_set = sel_sets.add() + new_sel_set.name = uniqify("SelectionSet", sel_sets.keys()) # select newly created set - arm.active_selection_set = len(arm.selection_sets) - 1 + arm.active_selection_set = len(sel_sets) - 1 return {'FINISHED'} @@ -293,7 +290,7 @@ class POSE_OT_selection_set_assign(PluginOperator): if not (arm.active_selection_set < len(arm.selection_sets)): bpy.ops.wm.call_menu("INVOKE_DEFAULT", - name="POSE_MT_selection_set_create") + name="POSE_MT_selection_set_create") else: bpy.ops.pose.selection_set_assign('EXEC_DEFAULT') @@ -337,12 +334,22 @@ class POSE_OT_selection_set_select(NeedSelSetPluginOperator): bl_description = "Add Selection Set bones to current selection" bl_options = {'UNDO', 'REGISTER'} + selection_set_index = IntProperty( + name='Selection Set Index', + default=-1, + description='Which Selection Set to select; -1 uses the active Selection Set') + def execute(self, context): arm = context.object - act_sel_set = arm.selection_sets[arm.active_selection_set] + + if self.selection_set_index == -1: + idx = arm.active_selection_set + else: + idx = self.selection_set_index + sel_set = arm.selection_sets[idx] for bone in context.visible_pose_bones: - if bone.name in act_sel_set.bone_ids: + if bone.name in sel_set.bone_ids: bone.bone.select = True return {'FINISHED'} @@ -377,11 +384,44 @@ class POSE_OT_selection_set_add_and_assign(PluginOperator): return {'FINISHED'} +class POSE_OT_selection_set_copy(NeedSelSetPluginOperator): + bl_idname = "pose.selection_set_copy" + bl_label = "Copy Selection Set to Clipboard" + bl_description = "Converts the Selection Set to JSON and places it on the clipboard" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + context.window_manager.clipboard = to_json(context) + self.report({'INFO'}, 'Copied Selection Set to Clipboard') + return {'FINISHED'} + + +class POSE_OT_selection_set_paste(PluginOperator): + bl_idname = "pose.selection_set_paste" + bl_label = "Paste Selection Set from Clipboard" + bl_description = "Adds a new Selection Set from copied JSON on the clipboard" + bl_options = {'UNDO', 'REGISTER'} + + def execute(self, context): + import json + + try: + from_json(context, context.window_manager.clipboard) + except (json.JSONDecodeError, KeyError): + self.report({'ERROR'}, 'The clipboard does not contain a Selection Set') + else: + # Select the pasted Selection Set. + context.object.active_selection_set = len(context.object.selection_sets) - 1 + + return {'FINISHED'} + + # Registry #################################################################### classes = ( - POSE_MT_create_new_selection_set, + POSE_MT_selection_set_create, POSE_MT_selection_sets_specials, + POSE_MT_selection_sets, POSE_PT_selection_sets, POSE_UL_selection_set, SelectionEntry, @@ -396,23 +436,111 @@ classes = ( POSE_OT_selection_set_select, POSE_OT_selection_set_deselect, POSE_OT_selection_set_add_and_assign, + POSE_OT_selection_set_copy, + POSE_OT_selection_set_paste, ) +def add_sss_button(self, context): + self.layout.menu('POSE_MT_selection_sets') + + +def to_json(context) -> str: + """Convert the active bone selection set of the current rig to JSON.""" + import json + + arm = context.object + active_idx = arm.active_selection_set + sel_set = arm.selection_sets[active_idx] + + return json.dumps({ + 'name': sel_set.name, + 'bones': [bone_id.name for bone_id in sel_set.bone_ids] + }) + + +def from_json(context, as_json: str): + """Add the single bone selection set from JSON to the current rig.""" + import json + + sel_set = json.loads(as_json) + + sel_sets = context.object.selection_sets + new_sel_set = sel_sets.add() + new_sel_set.name = uniqify(sel_set['name'], sel_sets.keys()) + + for bone_name in sel_set['bones']: + bone_id = new_sel_set.bone_ids.add() + bone_id.name = bone_name + + +def uniqify(name: str, other_names: list) -> str: + """Return a unique name with .xxx suffix if necessary. + + >>> uniqify('hey', ['there']) + 'hey' + >>> uniqify('hey', ['hey.001', 'hey.005']) + 'hey' + >>> uniqify('hey', ['hey', 'hey.001', 'hey.005']) + 'hey.002' + >>> uniqify('hey', ['hey', 'hey.005', 'hey.001']) + 'hey.002' + >>> uniqify('hey', ['hey', 'hey.005', 'hey.001', 'hey.left']) + 'hey.002' + >>> uniqify('hey', ['hey', 'hey.001', 'hey.002']) + 'hey.003' + + It also works with a dict_keys object: + >>> uniqify('hey', {'hey': 1, 'hey.005': 1, 'hey.001': 1}.keys()) + 'hey.002' + """ + + if name not in other_names: + return name + + # Construct the list of numbers already in use. + offset = len(name) + 1 + others = (n[offset:] for n in other_names + if n.startswith(name + '.')) + numbers = sorted(int(suffix) for suffix in others + if suffix.isdigit()) + + # Find the first unused number. + min_index = 1 + for num in numbers: + if min_index < num: + break + min_index = num + 1 + return "{}.{:03d}".format(name, min_index) + + +# store keymaps here to access after registration +addon_keymaps = [] + + def register(): for cls in classes: bpy.utils.register_class(cls) bpy.types.Object.selection_sets = CollectionProperty( - type=SelectionSet, - name="Selection Sets", - description="List of groups of bones for easy selection" - ) + type=SelectionSet, + name="Selection Sets", + description="List of groups of bones for easy selection" + ) bpy.types.Object.active_selection_set = IntProperty( - name="Active Selection Set", - description="Index of the currently active selection set", - default=0 - ) + name="Active Selection Set", + description="Index of the currently active selection set", + default=0 + ) + + wm = bpy.context.window_manager + km = wm.keyconfigs.addon.keymaps.new(name='Pose') + + kmi = km.keymap_items.new('wm.call_menu', 'W', 'PRESS', alt=True, shift=True) + kmi.properties.name = 'POSE_MT_selection_sets' + addon_keymaps.append((km, kmi)) + + bpy.types.VIEW3D_MT_select_pose.append(add_sss_button) def unregister(): @@ -422,6 +550,14 @@ def unregister(): del bpy.types.Object.selection_sets del bpy.types.Object.active_selection_set + # handle the keymap + for km, kmi in addon_keymaps: + km.keymap_items.remove(kmi) + addon_keymaps.clear() + if __name__ == "__main__": + import doctest + + doctest.testmod() register() diff --git a/camera_turnaround.py b/camera_turnaround.py index d1a27e98..6a6e710f 100644 --- a/camera_turnaround.py +++ b/camera_turnaround.py @@ -21,7 +21,7 @@ bl_info = { "author": "Antonio Vazquez (antonioya)", "version": (0, 2, 5), "blender": (2, 68, 0), - "location": "View3D > Toolshelf > Turnaround camera", + "location": "View3D > Toolshelf > Animation Tab > Turnaround Camera", "description": "Add a camera rotation around selected object", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" "Scripts/Animation/TurnaroundCamera", @@ -58,7 +58,7 @@ class RunAction(Operator): scene = context.scene turn_camera = scene.turn_camera selectobject = context.active_object - camera = bpy.data.objects[bpy.context.scene.camera.name] + camera = context.scene.camera savedcursor = bpy.context.scene.cursor_location.copy() # cursor position savedframe = scene.frame_current if turn_camera.use_cursor is False: @@ -68,7 +68,7 @@ class RunAction(Operator): # Create empty and parent # ------------------------- bpy.ops.object.empty_add(type='PLAIN_AXES') - myempty = bpy.data.objects[bpy.context.active_object.name] + myempty = context.active_object myempty.location = selectobject.location savedstate = myempty.matrix_world @@ -89,7 +89,7 @@ class RunAction(Operator): # ------------------------- bpy.ops.object.select_all(False) myempty.select = True - bpy.context.scene.objects.active = myempty + context.scene.objects.active = myempty # save current configuration savedinterpolation = context.user_preferences.edit.keyframe_new_interpolation_type # change interpolation mode @@ -97,7 +97,7 @@ class RunAction(Operator): # create first frame myempty.rotation_euler = (0, 0, 0) myempty.empty_draw_size = 0.1 - bpy.context.scene.frame_set(scene.frame_start) + context.scene.frame_set(scene.frame_start) myempty.keyframe_insert(data_path='rotation_euler', frame=scene.frame_start) # Clear the Camera Animations if the option is checked diff --git a/depsgraph_debug.py b/depsgraph_debug.py index 9e4c67f0..438d4885 100644 --- a/depsgraph_debug.py +++ b/depsgraph_debug.py @@ -40,8 +40,8 @@ def _get_depsgraph(context): if bpy.app.version < (2, 80, 0,): return scene.depsgraph else: - scene_layer = scene.view_layers.active - return scene_layer.depsgraph + view_layer = context.view_layer + return view_layer.depsgraph ############################################################################### diff --git a/development_iskeyfree.py b/development_iskeyfree.py index 0408a648..7851afee 100644 --- a/development_iskeyfree.py +++ b/development_iskeyfree.py @@ -21,33 +21,33 @@ bl_info = { "name": "Is key Free", "author": "Antonio Vazquez (antonioya)", - "version": (1, 0, 2), + "version": (1, 1, 1), "blender": (2, 6, 9), "location": "Text Editor > Props Shelf (Ctrl/t > IsKeyFree Tools", - "description": "Find free shortcuts and inform of used keys", + "description": "Find free shortcuts, inform about used and print a key list", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6" "/Py/Scripts/Development/IsKeyFree", - "category": "Development"} + "category": "Development" +} import bpy from bpy.props import ( - StringProperty, - BoolProperty, - EnumProperty, - PointerProperty, - ) + BoolProperty, + EnumProperty, + StringProperty, + PointerProperty, +) from bpy.types import ( - Operator, - Panel, - PropertyGroup, - ) + Operator, + Panel, + PropertyGroup, +) # ------------------------------------------------------ # Class to find keymaps # ------------------------------------------------------ - class MyChecker(): lastfind = None lastkey = None @@ -191,16 +191,11 @@ mychecker = MyChecker() # Global class handler # ------------------------------------------------------ # Button: Class for search button # ------------------------------------------------------ - - class RunActionCheck(Operator): bl_idname = "iskeyfree.action_check" bl_label = "" bl_description = "Verify if the selected shortcut is free" - # ------------------------------ - # Execute - # ------------------------------ # noinspection PyUnusedLocal def execute(self, context): scene = context.scene.is_keyfree @@ -239,6 +234,8 @@ class UIControlPanel(Panel): row = layout.row() row.prop(scene, "numpad") + layout.operator("iskeyfree.run_export_keys", icon="FILE_TEXT") + global mychecker mylist = mychecker.getlist() oldcontext = None @@ -270,8 +267,6 @@ class UIControlPanel(Panel): # ------------------------------------------------------ # Update key (special values) event handler # ------------------------------------------------------ - - # noinspection PyUnusedLocal def update_data(self, context): scene = context.scene.is_keyfree @@ -281,230 +276,320 @@ def update_data(self, context): class IskeyFreeProperties(PropertyGroup): data = StringProperty( - name="Key", maxlen=32, - description="Shortcut to verify" - ) + name="Key", maxlen=32, + description="Shortcut to verify" + ) use_crtl = BoolProperty( - name="Ctrl", - description="Ctrl key used in shortcut", - default=False - ) + name="Ctrl", + description="Ctrl key used in shortcut", + default=False + ) use_alt = BoolProperty( - name="Alt", - description="Alt key used in shortcut", - default=False - ) + name="Alt", + description="Alt key used in shortcut", + default=False + ) use_shift = BoolProperty( - name="Shift", - description="Shift key used in shortcut", - default=False - ) + name="Shift", + description="Shift key used in shortcut", + default=False + ) use_oskey = BoolProperty( - name="OsKey", - description="Operating system key used in shortcut", - default=False - ) + name="OsKey", + description="Operating system key used in shortcut", + default=False + ) numpad = EnumProperty( - items=(('NONE', "Select key", ""), - ("LEFTMOUSE", "LEFTMOUSE", ""), - ("MIDDLEMOUSE", "MIDDLEMOUSE", ""), - ("RIGHTMOUSE", "RIGHTMOUSE", ""), - ("BUTTON4MOUSE", "BUTTON4MOUSE", ""), - ("BUTTON5MOUSE", "BUTTON5MOUSE", ""), - ("BUTTON6MOUSE", "BUTTON6MOUSE", ""), - ("BUTTON7MOUSE", "BUTTON7MOUSE", ""), - ("ACTIONMOUSE", "ACTIONMOUSE", ""), - ("SELECTMOUSE", "SELECTMOUSE", ""), - ("MOUSEMOVE", "MOUSEMOVE", ""), - ("INBETWEEN_MOUSEMOVE", "INBETWEEN_MOUSEMOVE", ""), - ("TRACKPADPAN", "TRACKPADPAN", ""), - ("TRACKPADZOOM", "TRACKPADZOOM", ""), - ("MOUSEROTATE", "MOUSEROTATE", ""), - ("WHEELUPMOUSE", "WHEELUPMOUSE", ""), - ("WHEELDOWNMOUSE", "WHEELDOWNMOUSE", ""), - ("WHEELINMOUSE", "WHEELINMOUSE", ""), - ("WHEELOUTMOUSE", "WHEELOUTMOUSE", ""), - ("EVT_TWEAK_L", "EVT_TWEAK_L", ""), - ("EVT_TWEAK_M", "EVT_TWEAK_M", ""), - ("EVT_TWEAK_R", "EVT_TWEAK_R", ""), - ("EVT_TWEAK_A", "EVT_TWEAK_A", ""), - ("EVT_TWEAK_S", "EVT_TWEAK_S", ""), - ("A", "A", ""), - ("B", "B", ""), - ("C", "C", ""), - ("D", "D", ""), - ("E", "E", ""), - ("F", "F", ""), - ("G", "G", ""), - ("H", "H", ""), - ("I", "I", ""), - ("J", "J", ""), - ("K", "K", ""), - ("L", "L", ""), - ("M", "M", ""), - ("N", "N", ""), - ("O", "O", ""), - ("P", "P", ""), - ("Q", "Q", ""), - ("R", "R", ""), - ("S", "S", ""), - ("T", "T", ""), - ("U", "U", ""), - ("V", "V", ""), - ("W", "W", ""), - ("X", "X", ""), - ("Y", "Y", ""), - ("Z", "Z", ""), - ("ZERO", "ZERO", ""), - ("ONE", "ONE", ""), - ("TWO", "TWO", ""), - ("THREE", "THREE", ""), - ("FOUR", "FOUR", ""), - ("FIVE", "FIVE", ""), - ("SIX", "SIX", ""), - ("SEVEN", "SEVEN", ""), - ("EIGHT", "EIGHT", ""), - ("NINE", "NINE", ""), - ("LEFT_CTRL", "LEFT_CTRL", ""), - ("LEFT_ALT", "LEFT_ALT", ""), - ("LEFT_SHIFT", "LEFT_SHIFT", ""), - ("RIGHT_ALT", "RIGHT_ALT", ""), - ("RIGHT_CTRL", "RIGHT_CTRL", ""), - ("RIGHT_SHIFT", "RIGHT_SHIFT", ""), - ("OSKEY", "OSKEY", ""), - ("GRLESS", "GRLESS", ""), - ("ESC", "ESC", ""), - ("TAB", "TAB", ""), - ("RET", "RET", ""), - ("SPACE", "SPACE", ""), - ("LINE_FEED", "LINE_FEED", ""), - ("BACK_SPACE", "BACK_SPACE", ""), - ("DEL", "DEL", ""), - ("SEMI_COLON", "SEMI_COLON", ""), - ("PERIOD", "PERIOD", ""), - ("COMMA", "COMMA", ""), - ("QUOTE", "QUOTE", ""), - ("ACCENT_GRAVE", "ACCENT_GRAVE", ""), - ("MINUS", "MINUS", ""), - ("SLASH", "SLASH", ""), - ("BACK_SLASH", "BACK_SLASH", ""), - ("EQUAL", "EQUAL", ""), - ("LEFT_BRACKET", "LEFT_BRACKET", ""), - ("RIGHT_BRACKET", "RIGHT_BRACKET", ""), - ("LEFT_ARROW", "LEFT_ARROW", ""), - ("DOWN_ARROW", "DOWN_ARROW", ""), - ("RIGHT_ARROW", "RIGHT_ARROW", ""), - ("UP_ARROW", "UP_ARROW", ""), - ("NUMPAD_1", "NUMPAD_1", ""), - ("NUMPAD_2", "NUMPAD_2", ""), - ("NUMPAD_3", "NUMPAD_3", ""), - ("NUMPAD_4", "NUMPAD_4", ""), - ("NUMPAD_5", "NUMPAD_5", ""), - ("NUMPAD_6", "NUMPAD_6", ""), - ("NUMPAD_7", "NUMPAD_7", ""), - ("NUMPAD_8", "NUMPAD_8", ""), - ("NUMPAD_9", "NUMPAD_9", ""), - ("NUMPAD_0", "NUMPAD_0", ""), - ("NUMPAD_PERIOD", "NUMPAD_PERIOD", ""), - ("NUMPAD_SLASH", "NUMPAD_SLASH", ""), - ("NUMPAD_ASTERIX", "NUMPAD_ASTERIX", ""), - ("NUMPAD_MINUS", "NUMPAD_MINUS", ""), - ("NUMPAD_ENTER", "NUMPAD_ENTER", ""), - ("NUMPAD_PLUS", "NUMPAD_PLUS", ""), - ("F1", "F1", ""), - ("F2", "F2", ""), - ("F3", "F3", ""), - ("F4", "F4", ""), - ("F5", "F5", ""), - ("F6", "F6", ""), - ("F7", "F7", ""), - ("F8", "F8", ""), - ("F9", "F9", ""), - ("F10", "F10", ""), - ("F11", "F11", ""), - ("F12", "F12", ""), - ("F13", "F13", ""), - ("F14", "F14", ""), - ("F15", "F15", ""), - ("F16", "F16", ""), - ("F17", "F17", ""), - ("F18", "F18", ""), - ("F19", "F19", ""), - ("PAUSE", "PAUSE", ""), - ("INSERT", "INSERT", ""), - ("HOME", "HOME", ""), - ("PAGE_UP", "PAGE_UP", ""), - ("PAGE_DOWN", "PAGE_DOWN", ""), - ("END", "END", ""), - ("MEDIA_PLAY", "MEDIA_PLAY", ""), - ("MEDIA_STOP", "MEDIA_STOP", ""), - ("MEDIA_FIRST", "MEDIA_FIRST", ""), - ("MEDIA_LAST", "MEDIA_LAST", ""), - ("TEXTINPUT", "TEXTINPUT", ""), - ("WINDOW_DEACTIVATE", "WINDOW_DEACTIVATE", ""), - ("TIMER", "TIMER", ""), - ("TIMER0", "TIMER0", ""), - ("TIMER1", "TIMER1", ""), - ("TIMER2", "TIMER2", ""), - ("TIMER_JOBS", "TIMER_JOBS", ""), - ("TIMER_AUTOSAVE", "TIMER_AUTOSAVE", ""), - ("TIMER_REPORT", "TIMER_REPORT", ""), - ("TIMERREGION", "TIMERREGION", ""), - ("NDOF_MOTION", "NDOF_MOTION", ""), - ("NDOF_BUTTON_MENU", "NDOF_BUTTON_MENU", ""), - ("NDOF_BUTTON_FIT", "NDOF_BUTTON_FIT", ""), - ("NDOF_BUTTON_TOP", "NDOF_BUTTON_TOP", ""), - ("NDOF_BUTTON_BOTTOM", "NDOF_BUTTON_BOTTOM", ""), - ("NDOF_BUTTON_LEFT", "NDOF_BUTTON_LEFT", ""), - ("NDOF_BUTTON_RIGHT", "NDOF_BUTTON_RIGHT", ""), - ("NDOF_BUTTON_FRONT", "NDOF_BUTTON_FRONT", ""), - ("NDOF_BUTTON_BACK", "NDOF_BUTTON_BACK", ""), - ("NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO1", ""), - ("NDOF_BUTTON_ISO2", "NDOF_BUTTON_ISO2", ""), - ("NDOF_BUTTON_ROLL_CW", "NDOF_BUTTON_ROLL_CW", ""), - ("NDOF_BUTTON_ROLL_CCW", "NDOF_BUTTON_ROLL_CCW", ""), - ("NDOF_BUTTON_SPIN_CW", "NDOF_BUTTON_SPIN_CW", ""), - ("NDOF_BUTTON_SPIN_CCW", "NDOF_BUTTON_SPIN_CCW", ""), - ("NDOF_BUTTON_TILT_CW", "NDOF_BUTTON_TILT_CW", ""), - ("NDOF_BUTTON_TILT_CCW", "NDOF_BUTTON_TILT_CCW", ""), - ("NDOF_BUTTON_ROTATE", "NDOF_BUTTON_ROTATE", ""), - ("NDOF_BUTTON_PANZOOM", "NDOF_BUTTON_PANZOOM", ""), - ("NDOF_BUTTON_DOMINANT", "NDOF_BUTTON_DOMINANT", ""), - ("NDOF_BUTTON_PLUS", "NDOF_BUTTON_PLUS", ""), - ("NDOF_BUTTON_MINUS", "NDOF_BUTTON_MINUS", ""), - ("NDOF_BUTTON_ESC", "NDOF_BUTTON_ESC", ""), - ("NDOF_BUTTON_ALT", "NDOF_BUTTON_ALT", ""), - ("NDOF_BUTTON_SHIFT", "NDOF_BUTTON_SHIFT", ""), - ("NDOF_BUTTON_CTRL", "NDOF_BUTTON_CTRL", ""), - ("NDOF_BUTTON_1", "NDOF_BUTTON_1", ""), - ("NDOF_BUTTON_2", "NDOF_BUTTON_2", ""), - ("NDOF_BUTTON_3", "NDOF_BUTTON_3", ""), - ("NDOF_BUTTON_4", "NDOF_BUTTON_4", ""), - ("NDOF_BUTTON_5", "NDOF_BUTTON_5", ""), - ("NDOF_BUTTON_6", "NDOF_BUTTON_6", ""), - ("NDOF_BUTTON_7", "NDOF_BUTTON_7", ""), - ("NDOF_BUTTON_8", "NDOF_BUTTON_8", ""), - ("NDOF_BUTTON_9", "NDOF_BUTTON_9", ""), - ("NDOF_BUTTON_10", "NDOF_BUTTON_10", ""), - ("NDOF_BUTTON_A", "NDOF_BUTTON_A", ""), - ("NDOF_BUTTON_B", "NDOF_BUTTON_B", ""), - ("NDOF_BUTTON_C", "NDOF_BUTTON_C", "") - ), - name="Quick Type", - description="Enter key code in find text", - update=update_data - ) + items=( + ('NONE', "Select key", ""), + ("LEFTMOUSE", "LEFTMOUSE", ""), + ("MIDDLEMOUSE", "MIDDLEMOUSE", ""), + ("RIGHTMOUSE", "RIGHTMOUSE", ""), + ("BUTTON4MOUSE", "BUTTON4MOUSE", ""), + ("BUTTON5MOUSE", "BUTTON5MOUSE", ""), + ("BUTTON6MOUSE", "BUTTON6MOUSE", ""), + ("BUTTON7MOUSE", "BUTTON7MOUSE", ""), + ("ACTIONMOUSE", "ACTIONMOUSE", ""), + ("SELECTMOUSE", "SELECTMOUSE", ""), + ("MOUSEMOVE", "MOUSEMOVE", ""), + ("INBETWEEN_MOUSEMOVE", "INBETWEEN_MOUSEMOVE", ""), + ("TRACKPADPAN", "TRACKPADPAN", ""), + ("TRACKPADZOOM", "TRACKPADZOOM", ""), + ("MOUSEROTATE", "MOUSEROTATE", ""), + ("WHEELUPMOUSE", "WHEELUPMOUSE", ""), + ("WHEELDOWNMOUSE", "WHEELDOWNMOUSE", ""), + ("WHEELINMOUSE", "WHEELINMOUSE", ""), + ("WHEELOUTMOUSE", "WHEELOUTMOUSE", ""), + ("EVT_TWEAK_L", "EVT_TWEAK_L", ""), + ("EVT_TWEAK_M", "EVT_TWEAK_M", ""), + ("EVT_TWEAK_R", "EVT_TWEAK_R", ""), + ("EVT_TWEAK_A", "EVT_TWEAK_A", ""), + ("EVT_TWEAK_S", "EVT_TWEAK_S", ""), + ("A", "A", ""), + ("B", "B", ""), + ("C", "C", ""), + ("D", "D", ""), + ("E", "E", ""), + ("F", "F", ""), + ("G", "G", ""), + ("H", "H", ""), + ("I", "I", ""), + ("J", "J", ""), + ("K", "K", ""), + ("L", "L", ""), + ("M", "M", ""), + ("N", "N", ""), + ("O", "O", ""), + ("P", "P", ""), + ("Q", "Q", ""), + ("R", "R", ""), + ("S", "S", ""), + ("T", "T", ""), + ("U", "U", ""), + ("V", "V", ""), + ("W", "W", ""), + ("X", "X", ""), + ("Y", "Y", ""), + ("Z", "Z", ""), + ("ZERO", "ZERO", ""), + ("ONE", "ONE", ""), + ("TWO", "TWO", ""), + ("THREE", "THREE", ""), + ("FOUR", "FOUR", ""), + ("FIVE", "FIVE", ""), + ("SIX", "SIX", ""), + ("SEVEN", "SEVEN", ""), + ("EIGHT", "EIGHT", ""), + ("NINE", "NINE", ""), + ("LEFT_CTRL", "LEFT_CTRL", ""), + ("LEFT_ALT", "LEFT_ALT", ""), + ("LEFT_SHIFT", "LEFT_SHIFT", ""), + ("RIGHT_ALT", "RIGHT_ALT", ""), + ("RIGHT_CTRL", "RIGHT_CTRL", ""), + ("RIGHT_SHIFT", "RIGHT_SHIFT", ""), + ("OSKEY", "OSKEY", ""), + ("GRLESS", "GRLESS", ""), + ("ESC", "ESC", ""), + ("TAB", "TAB", ""), + ("RET", "RET", ""), + ("SPACE", "SPACE", ""), + ("LINE_FEED", "LINE_FEED", ""), + ("BACK_SPACE", "BACK_SPACE", ""), + ("DEL", "DEL", ""), + ("SEMI_COLON", "SEMI_COLON", ""), + ("PERIOD", "PERIOD", ""), + ("COMMA", "COMMA", ""), + ("QUOTE", "QUOTE", ""), + ("ACCENT_GRAVE", "ACCENT_GRAVE", ""), + ("MINUS", "MINUS", ""), + ("SLASH", "SLASH", ""), + ("BACK_SLASH", "BACK_SLASH", ""), + ("EQUAL", "EQUAL", ""), + ("LEFT_BRACKET", "LEFT_BRACKET", ""), + ("RIGHT_BRACKET", "RIGHT_BRACKET", ""), + ("LEFT_ARROW", "LEFT_ARROW", ""), + ("DOWN_ARROW", "DOWN_ARROW", ""), + ("RIGHT_ARROW", "RIGHT_ARROW", ""), + ("UP_ARROW", "UP_ARROW", ""), + ("NUMPAD_1", "NUMPAD_1", ""), + ("NUMPAD_2", "NUMPAD_2", ""), + ("NUMPAD_3", "NUMPAD_3", ""), + ("NUMPAD_4", "NUMPAD_4", ""), + ("NUMPAD_5", "NUMPAD_5", ""), + ("NUMPAD_6", "NUMPAD_6", ""), + ("NUMPAD_7", "NUMPAD_7", ""), + ("NUMPAD_8", "NUMPAD_8", ""), + ("NUMPAD_9", "NUMPAD_9", ""), + ("NUMPAD_0", "NUMPAD_0", ""), + ("NUMPAD_PERIOD", "NUMPAD_PERIOD", ""), + ("NUMPAD_SLASH", "NUMPAD_SLASH", ""), + ("NUMPAD_ASTERIX", "NUMPAD_ASTERIX", ""), + ("NUMPAD_MINUS", "NUMPAD_MINUS", ""), + ("NUMPAD_ENTER", "NUMPAD_ENTER", ""), + ("NUMPAD_PLUS", "NUMPAD_PLUS", ""), + ("F1", "F1", ""), + ("F2", "F2", ""), + ("F3", "F3", ""), + ("F4", "F4", ""), + ("F5", "F5", ""), + ("F6", "F6", ""), + ("F7", "F7", ""), + ("F8", "F8", ""), + ("F9", "F9", ""), + ("F10", "F10", ""), + ("F11", "F11", ""), + ("F12", "F12", ""), + ("F13", "F13", ""), + ("F14", "F14", ""), + ("F15", "F15", ""), + ("F16", "F16", ""), + ("F17", "F17", ""), + ("F18", "F18", ""), + ("F19", "F19", ""), + ("PAUSE", "PAUSE", ""), + ("INSERT", "INSERT", ""), + ("HOME", "HOME", ""), + ("PAGE_UP", "PAGE_UP", ""), + ("PAGE_DOWN", "PAGE_DOWN", ""), + ("END", "END", ""), + ("MEDIA_PLAY", "MEDIA_PLAY", ""), + ("MEDIA_STOP", "MEDIA_STOP", ""), + ("MEDIA_FIRST", "MEDIA_FIRST", ""), + ("MEDIA_LAST", "MEDIA_LAST", ""), + ("TEXTINPUT", "TEXTINPUT", ""), + ("WINDOW_DEACTIVATE", "WINDOW_DEACTIVATE", ""), + ("TIMER", "TIMER", ""), + ("TIMER0", "TIMER0", ""), + ("TIMER1", "TIMER1", ""), + ("TIMER2", "TIMER2", ""), + ("TIMER_JOBS", "TIMER_JOBS", ""), + ("TIMER_AUTOSAVE", "TIMER_AUTOSAVE", ""), + ("TIMER_REPORT", "TIMER_REPORT", ""), + ("TIMERREGION", "TIMERREGION", ""), + ("NDOF_MOTION", "NDOF_MOTION", ""), + ("NDOF_BUTTON_MENU", "NDOF_BUTTON_MENU", ""), + ("NDOF_BUTTON_FIT", "NDOF_BUTTON_FIT", ""), + ("NDOF_BUTTON_TOP", "NDOF_BUTTON_TOP", ""), + ("NDOF_BUTTON_BOTTOM", "NDOF_BUTTON_BOTTOM", ""), + ("NDOF_BUTTON_LEFT", "NDOF_BUTTON_LEFT", ""), + ("NDOF_BUTTON_RIGHT", "NDOF_BUTTON_RIGHT", ""), + ("NDOF_BUTTON_FRONT", "NDOF_BUTTON_FRONT", ""), + ("NDOF_BUTTON_BACK", "NDOF_BUTTON_BACK", ""), + ("NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO1", ""), + ("NDOF_BUTTON_ISO2", "NDOF_BUTTON_ISO2", ""), + ("NDOF_BUTTON_ROLL_CW", "NDOF_BUTTON_ROLL_CW", ""), + ("NDOF_BUTTON_ROLL_CCW", "NDOF_BUTTON_ROLL_CCW", ""), + ("NDOF_BUTTON_SPIN_CW", "NDOF_BUTTON_SPIN_CW", ""), + ("NDOF_BUTTON_SPIN_CCW", "NDOF_BUTTON_SPIN_CCW", ""), + ("NDOF_BUTTON_TILT_CW", "NDOF_BUTTON_TILT_CW", ""), + ("NDOF_BUTTON_TILT_CCW", "NDOF_BUTTON_TILT_CCW", ""), + ("NDOF_BUTTON_ROTATE", "NDOF_BUTTON_ROTATE", ""), + ("NDOF_BUTTON_PANZOOM", "NDOF_BUTTON_PANZOOM", ""), + ("NDOF_BUTTON_DOMINANT", "NDOF_BUTTON_DOMINANT", ""), + ("NDOF_BUTTON_PLUS", "NDOF_BUTTON_PLUS", ""), + ("NDOF_BUTTON_MINUS", "NDOF_BUTTON_MINUS", ""), + ("NDOF_BUTTON_ESC", "NDOF_BUTTON_ESC", ""), + ("NDOF_BUTTON_ALT", "NDOF_BUTTON_ALT", ""), + ("NDOF_BUTTON_SHIFT", "NDOF_BUTTON_SHIFT", ""), + ("NDOF_BUTTON_CTRL", "NDOF_BUTTON_CTRL", ""), + ("NDOF_BUTTON_1", "NDOF_BUTTON_1", ""), + ("NDOF_BUTTON_2", "NDOF_BUTTON_2", ""), + ("NDOF_BUTTON_3", "NDOF_BUTTON_3", ""), + ("NDOF_BUTTON_4", "NDOF_BUTTON_4", ""), + ("NDOF_BUTTON_5", "NDOF_BUTTON_5", ""), + ("NDOF_BUTTON_6", "NDOF_BUTTON_6", ""), + ("NDOF_BUTTON_7", "NDOF_BUTTON_7", ""), + ("NDOF_BUTTON_8", "NDOF_BUTTON_8", ""), + ("NDOF_BUTTON_9", "NDOF_BUTTON_9", ""), + ("NDOF_BUTTON_10", "NDOF_BUTTON_10", ""), + ("NDOF_BUTTON_A", "NDOF_BUTTON_A", ""), + ("NDOF_BUTTON_B", "NDOF_BUTTON_B", ""), + ("NDOF_BUTTON_C", "NDOF_BUTTON_C", "") + ), + name="Quick Type", + description="Enter key code in find text", + update=update_data + ) + + +class IsKeyFreeRunExportKeys(Operator): + bl_idname = "iskeyfree.run_export_keys" + bl_label = "List all Shortcuts" + bl_description = ("List all existing shortcuts in a text block\n" + "The newly generated list will be made active in the Text Editor\n" + "To access the previous ones, select them from the Header dropdown") + + def all_shortcuts_name(self, context): + new_name, def_name, ext = "", "All_Shortcuts", ".txt" + suffix = 1 + try: + # first slap a simple linear count + 1 for numeric suffix, if it fails + # harvest for the rightmost numbers and append the max value + list_txt = [] + data_txt = bpy.data.texts + list_txt = [txt.name for txt in data_txt if txt.name.startswith("All_Shortcuts")] + new_name = "{}_{}{}".format(def_name, len(list_txt) + 1, ext) + + if new_name in list_txt: + from re import findall + test_num = [findall("\d+", words) for words in list_txt] + suffix += max([int(l[-1]) for l in test_num]) + new_name = "{}_{}{}".format(def_name, suffix, ext) + return new_name + except: + return None + + def execute(self, context): + wm = bpy.context.window_manager + from collections import defaultdict + mykeys = defaultdict(list) + file_name = self.all_shortcuts_name(context) or "All_Shortcut.txt" + start_note = "# Note: Some of the shortcuts entries don't have a name. Mostly Modal stuff\n" + col_width, col_shortcuts = 2, 2 + + for ctx_type, keyboardmap in wm.keyconfigs.user.keymaps.items(): + for myitem in keyboardmap.keymap_items: + padding = len(myitem.name) + col_width = padding + 2 if padding > col_width else col_width + + short_type = myitem.type if myitem.type else "UNKNOWN" + is_ctrl = " Ctrl" if myitem.ctrl is True else "" + is_alt = " Alt" if myitem.alt is True else "" + is_shift = " Shift" if myitem.shift is True else "" + is_oskey = " OsKey" if myitem.oskey is True else "" + short_cuts = "{}{}{}{}{}".format(short_type, is_ctrl, is_alt, is_shift, is_oskey) + + t = ( + myitem.name if myitem.name else "No Name", + short_cuts, + ) + mykeys[ctx_type].append(t) + padding_s = len(short_cuts) + 2 + col_shortcuts = padding_s if padding_s > col_shortcuts else col_shortcuts + + max_line = col_shortcuts + col_width + 4 + textblock = bpy.data.texts.new(file_name) + total = sum([len(mykeys[ctxs]) for ctxs in mykeys]) + textblock.write('# %d Total Shortcuts\n\n' % total) + textblock.write(start_note) + + for ctx in mykeys: + textblock.write("\n[%s]\nEntries: %s\n\n" % (ctx, len(mykeys[ctx]))) + line_k = sorted(mykeys[ctx]) + for keys in line_k: + add_ticks = "-" * (max_line - (len(keys[0]) + len(keys[1]))) + entries = "{ticks} {entry}".format(ticks=add_ticks, entry=keys[1]) + textblock.write("{name} {entry}\n".format(name=keys[0], entry=entries)) + + textblock.write("\n\n") + + # try to set the created text block to active + if context.area.type in {"TEXT_EDITOR"}: + bpy.context.space_data.text = bpy.data.texts[file_name] + + self.report({'INFO'}, "See %s textblock" % file_name) + + return {"FINISHED"} # ----------------------------------------------------- # Registration # ------------------------------------------------------ +classes = ( + IskeyFreeProperties, + RunActionCheck, + UIControlPanel, + IsKeyFreeRunExportKeys, +) + def register(): - bpy.utils.register_module(__name__) + for cls in classes: + bpy.utils.register_class(cls) bpy.types.Scene.is_keyfree = PointerProperty(type=IskeyFreeProperties) def unregister(): - bpy.utils.unregister_module(__name__) + for cls in classes: + bpy.utils.unregister_class(cls) del bpy.types.Scene.is_keyfree diff --git a/development_ui_classes.py b/development_ui_classes.py index 18b692f4..f03b7bb5 100644 --- a/development_ui_classes.py +++ b/development_ui_classes.py @@ -21,7 +21,7 @@ bl_info = { "name": "UI Classes Overview", "author": "lijenstina", - "version": (1, 0, 1), + "version": (1, 0, 2), "blender": (2, 78, 0), "location": "Text Editor > Properties", "description": "Print the UI classes in a text-block", @@ -33,15 +33,15 @@ bl_info = { import bpy from bpy.types import ( - Operator, - Panel, - PropertyGroup, - ) + Operator, + Panel, + PropertyGroup, +) from bpy.props import ( - BoolProperty, - EnumProperty, - PointerProperty, - ) + BoolProperty, + EnumProperty, + PointerProperty, +) class TEXT_PT_ui_cheat_sheet(Panel): @@ -128,7 +128,10 @@ class TEXT_OT_ui_cheat_sheet(Operator): textblock.write(('\n' if not searchable else '').join(sorted(op_string_ui[cls_name]))) textblock.write(close_line) - context.space_data.text = bpy.data.texts[file_name] + # try to set the created text block to active + if context.area.type in {"TEXT_EDITOR"}: + bpy.context.space_data.text = bpy.data.texts[file_name] + self.report({'INFO'}, "See %s textblock" % file_name) return {'FINISHED'} @@ -152,21 +155,21 @@ class text_ui_cheat_props(PropertyGroup): ('Header', "Headers", "Print Header UI types"), ), default='all', - ) + ) searchable = BoolProperty( name="Format searchable", description="Generate the list as Python dictionary,\n" "using the format Class: Path", default=False - ) + ) # Register classes = ( - TEXT_OT_ui_cheat_sheet, - TEXT_PT_ui_cheat_sheet, - text_ui_cheat_props - ) + TEXT_OT_ui_cheat_sheet, + TEXT_PT_ui_cheat_sheet, + text_ui_cheat_props +) def register(): diff --git a/io_export_after_effects.py b/io_export_after_effects.py index aafa8ecb..4a5c6b92 100644 --- a/io_export_after_effects.py +++ b/io_export_after_effects.py @@ -23,8 +23,8 @@ bl_info = { "description": "Export cameras, selected objects & camera solution " "3D Markers to Adobe After Effects CS3 and above", "author": "Bartek Skorupa", - "version": (0, 64), - "blender": (2, 69, 0), + "version": (0, 65), + "blender": (2, 79, 0), "location": "File > Export > Adobe After Effects (.jsx)", "warning": "", "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" @@ -150,7 +150,7 @@ def convert_name(name): # get object's blender's location rotation and scale and return AE's Position, Rotation/Orientation and scale # this function will be called for every object for every frame -def convert_transform_matrix(matrix, width, height, aspect, x_rot_correction=False): +def convert_transform_matrix(matrix, width, height, aspect, x_rot_correction=False, ae_size=100.0): # get blender transform data for ob b_loc = matrix.to_translation() @@ -159,9 +159,9 @@ def convert_transform_matrix(matrix, width, height, aspect, x_rot_correction=Fal # convert to AE Position Rotation and Scale # Axes in AE are different. AE's X is blender's X, AE's Y is negative Blender's Z, AE's Z is Blender's Y - x = (b_loc.x * 100.0) / aspect + width / 2.0 # calculate AE's X position - y = (-b_loc.z * 100.0) + (height / 2.0) # calculate AE's Y position - z = b_loc.y * 100.0 # calculate AE's Z position + x = (b_loc.x * ae_size) / aspect + width / 2.0 # calculate AE's X position + y = (-b_loc.z * ae_size) + (height / 2.0) # calculate AE's Y position + z = b_loc.y * ae_size # calculate AE's Z position # Convert rotations to match AE's orientation. rx = degrees(b_rot.x) # if not x_rot_correction - AE's X orientation = blender's X rotation if 'ZYX' euler. ry = -degrees(b_rot.y) # AE's Y orientation is negative blender's Y rotation if 'ZYX' euler @@ -240,7 +240,7 @@ def convert_lens(camera, width, height, aspect): # jsx script for AE creation -def write_jsx_file(file, data, selection, include_animation, include_active_cam, include_selected_cams, include_selected_objects, include_cam_bundles): +def write_jsx_file(file, data, selection, include_animation, include_active_cam, include_selected_cams, include_selected_objects, include_cam_bundles, ae_size): print("\n---------------------------\n- Export to After Effects -\n---------------------------") # store the current frame to restore it at the end of export @@ -382,7 +382,7 @@ def write_jsx_file(file, data, selection, include_animation, include_active_cam, # bundles are in camera space. Transpose to world space matrix = Matrix.Translation(cam.matrix_basis.copy() * track.bundle) # convert the position into AE space - ae_transform = convert_transform_matrix(matrix, data['width'], data['height'], data['aspect'], x_rot_correction=False) + ae_transform = convert_transform_matrix(matrix, data['width'], data['height'], data['aspect'], False, ae_size) js_data['bundles_cam'][name_ae]['position'] += '[%f,%f,%f],' % (ae_transform[0], ae_transform[1], ae_transform[2]) # get all keyframes for each object and store in dico @@ -407,7 +407,7 @@ def write_jsx_file(file, data, selection, include_animation, include_active_cam, # get cam name name_ae = active_cam_name # convert cam transform properties to AE space - ae_transform = convert_transform_matrix(active_cam.matrix_world.copy(), data['width'], data['height'], data['aspect'], x_rot_correction=True) + ae_transform = convert_transform_matrix(active_cam.matrix_world.copy(), data['width'], data['height'], data['aspect'], True, ae_size) # convert Blender's lens to AE's zoom in pixels zoom = convert_lens(active_cam, data['width'], data['height'], data['aspect']) # store all values in dico @@ -437,7 +437,7 @@ def write_jsx_file(file, data, selection, include_animation, include_active_cam, # get cam name name_ae = selection['cameras'][i][1] # convert cam transform properties to AE space - ae_transform = convert_transform_matrix(cam[0].matrix_world.copy(), data['width'], data['height'], data['aspect'], x_rot_correction=True) + ae_transform = convert_transform_matrix(cam[0].matrix_world.copy(), data['width'], data['height'], data['aspect'], True, ae_size) # convert Blender's lens to AE's zoom in pixels zoom = convert_lens(cam[0], data['width'], data['height'], data['aspect']) # store all values in dico @@ -475,7 +475,7 @@ def write_jsx_file(file, data, selection, include_animation, include_active_cam, name_ae = selection['lights'][i][1] type = selection['lights'][i][0].data.type # convert ob transform properties to AE space - ae_transform = convert_transform_matrix(ob[0].matrix_world.copy(), data['width'], data['height'], data['aspect'], x_rot_correction=True) + ae_transform = convert_transform_matrix(ob[0].matrix_world.copy(), data['width'], data['height'], data['aspect'], True, ae_size) color = ob[0].data.color # store all values in dico position = '[%f,%f,%f],' % (ae_transform[0], ae_transform[1], ae_transform[2]) @@ -522,7 +522,7 @@ def write_jsx_file(file, data, selection, include_animation, include_active_cam, # get object name name_ae = selection['nulls'][i][1] # convert ob transform properties to AE space - ae_transform = convert_transform_matrix(ob[0].matrix_world.copy(), data['width'], data['height'], data['aspect'], x_rot_correction=True) + ae_transform = convert_transform_matrix(ob[0].matrix_world.copy(), data['width'], data['height'], data['aspect'], True, ae_size) # store all values in dico position = '[%f,%f,%f],' % (ae_transform[0], ae_transform[1], ae_transform[2]) orientation = '[%f,%f,%f],' % (ae_transform[3], ae_transform[4], ae_transform[5]) @@ -676,10 +676,10 @@ def write_jsx_file(file, data, selection, include_animation, include_active_cam, ########################################## -def main(file, context, include_animation, include_active_cam, include_selected_cams, include_selected_objects, include_cam_bundles): +def main(file, context, include_animation, include_active_cam, include_selected_cams, include_selected_objects, include_cam_bundles, ae_size): data = get_comp_data(context) selection = get_selected(context) - write_jsx_file(file, data, selection, include_animation, include_active_cam, include_selected_cams, include_selected_objects, include_cam_bundles) + write_jsx_file(file, data, selection, include_animation, include_active_cam, include_selected_cams, include_selected_objects, include_cam_bundles, ae_size) print ("\nExport to After Effects Completed") return {'FINISHED'} @@ -688,7 +688,7 @@ def main(file, context, include_animation, include_active_cam, include_selected_ ########################################## from bpy_extras.io_utils import ExportHelper -from bpy.props import StringProperty, BoolProperty +from bpy.props import StringProperty, BoolProperty, FloatProperty class ExportJsx(bpy.types.Operator, ExportHelper): @@ -728,11 +728,18 @@ class ExportJsx(bpy.types.Operator, ExportHelper): # description="Include 3D Markers of Object Motion Solution for selected cameras", # default=True, # ) + ae_size = FloatProperty( + name="AE Size", + description="Size of AE Composition (pixels per 1BU)", + default=100.0, + ) def draw(self, context): layout = self.layout box = layout.box() + box.label('Size fo AE Comp (pixels per 1 BU)') + box.prop(self, 'ae_size') box.label('Animation:') box.prop(self, 'include_animation') box.label('Include Cameras and Objects:') @@ -752,7 +759,7 @@ class ExportJsx(bpy.types.Operator, ExportHelper): return ok def execute(self, context): - return main(self.filepath, context, self.include_animation, self.include_active_cam, self.include_selected_cams, self.include_selected_objects, self.include_cam_bundles) + return main(self.filepath, context, self.include_animation, self.include_active_cam, self.include_selected_cams, self.include_selected_objects, self.include_cam_bundles, self.ae_size) def menu_func(self, context): diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py index 943df0b4..9611cb1f 100644 --- a/io_scene_fbx/__init__.py +++ b/io_scene_fbx/__init__.py @@ -21,7 +21,7 @@ bl_info = { "name": "FBX format", "author": "Campbell Barton, Bastien Montagne, Jens Restemeier", - "version": (3, 8, 4), + "version": (3, 9, 1), "blender": (2, 79, 1), "location": "File > Import-Export", "description": "FBX IO meshes, UV's, vertex colors, materials, textures, cameras, lamps and actions", diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py index 58bd4586..b75a8977 100644 --- a/io_scene_fbx/export_fbx_bin.py +++ b/io_scene_fbx/export_fbx_bin.py @@ -1896,8 +1896,9 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No ACNW(ob_obj.key, 'LCL_SCALING', force_key, force_sek, scale)) p_rots[ob_obj] = rot - animdata_shapes = OrderedDict() force_key = (simplify_fac == 0.0) + + animdata_shapes = OrderedDict() for me, (me_key, _shapes_key, shapes) in scene_data.data_deformers_shape.items(): # Ignore absolute shape keys for now! if not me.shape_keys.use_relative: @@ -1908,6 +1909,12 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No acnode.add_group(me_key, shape.name, shape.name, (shape.name,)) animdata_shapes[channel_key] = (acnode, me, shape) + animdata_cameras = OrderedDict() + for cam_obj, cam_key in scene_data.data_cameras.items(): + cam = cam_obj.bdata.data + acnode = AnimationCurveNodeWrapper(cam_key, 'CAMERA_FOCAL', force_key, force_sek, (cam.lens,)) + animdata_cameras[cam_key] = (acnode, cam) + currframe = f_start while currframe <= f_end: real_currframe = currframe - f_start if start_zero else currframe @@ -1927,6 +1934,8 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No ob_obj.dupli_list_clear() for anim_shape, me, shape in animdata_shapes.values(): anim_shape.add_keyframe(real_currframe, (shape.value * 100.0,)) + for anim_camera, camera in animdata_cameras.values(): + anim_camera.add_keyframe(real_currframe, (camera.lens,)) currframe += bake_step scene.frame_set(back_currframe, 0.0) @@ -1958,6 +1967,18 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No anim_data = animations[elem_key] = ("dummy_unused_key", OrderedDict()) anim_data[1][fbx_group] = (group_key, group, fbx_gname) + # And cameras' lens keys. + for cam_key, (anim_camera, camera) in animdata_cameras.items(): + final_keys = OrderedDict() + anim_camera.simplify(simplify_fac, bake_step, force_keep) + if not anim_camera: + continue + for elem_key, group_key, group, fbx_group, fbx_gname in anim_camera.get_final_data(scene, ref_id, force_keep): + anim_data = animations.get(elem_key) + if anim_data is None: + anim_data = animations[elem_key] = ("dummy_unused_key", OrderedDict()) + anim_data[1][fbx_group] = (group_key, group, fbx_gname) + astack_key = get_blender_anim_stack_key(scene, ref_id) alayer_key = get_blender_anim_layer_key(scene, ref_id) name = (get_blenderID_name(ref_id) if ref_id else scene.name).encode() @@ -2194,8 +2215,11 @@ def fbx_data_from_scene(scene, settings): if mod.show_render: use_org_data = False if not use_org_data: - tmp_me = ob.to_mesh(scene, apply_modifiers=True, - settings='RENDER' if settings.use_mesh_modifiers_render else 'PREVIEW') + tmp_me = ob.to_mesh( + scene, + apply_modifiers=settings.use_mesh_modifiers, + settings='RENDER' if settings.use_mesh_modifiers_render else 'PREVIEW', + ) data_meshes[ob_obj] = (get_blenderID_key(tmp_me), tmp_me, True) # Re-enable temporary disabled modifiers. for mod, show_render in tmp_mods: diff --git a/io_scene_fbx/fbx_utils.py b/io_scene_fbx/fbx_utils.py index 16a8256f..82e17fe2 100644 --- a/io_scene_fbx/fbx_utils.py +++ b/io_scene_fbx/fbx_utils.py @@ -94,6 +94,7 @@ FBX_LIGHT_DECAY_TYPES = { 'CONSTANT': 0, # None. 'INVERSE_LINEAR': 1, # Linear. 'INVERSE_SQUARE': 2, # Quadratic. + 'INVERSE_COEFFICIENTS': 2, # Quadratic... 'CUSTOM_CURVE': 2, # Quadratic. 'LINEAR_QUADRATIC_WEIGHTED': 2, # Quadratic. } @@ -728,6 +729,7 @@ class AnimationCurveNodeWrapper: 'LCL_ROTATION': ("Lcl Rotation", "R", ("X", "Y", "Z")), 'LCL_SCALING': ("Lcl Scaling", "S", ("X", "Y", "Z")), 'SHAPE_KEY': ("DeformPercent", "DeformPercent", ("DeformPercent",)), + 'CAMERA_FOCAL': ("FocalLength", "FocalLength", ("FocalLength",)), } def __init__(self, elem_key, kind, force_keying, force_startend_keying, default_values=...): diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py index 4757d2ef..446be0ff 100644 --- a/io_scene_fbx/import_fbx.py +++ b/io_scene_fbx/import_fbx.py @@ -64,6 +64,18 @@ MAT_CONVERT_LAMP = fbx_utils.MAT_CONVERT_LAMP.inverted() MAT_CONVERT_CAMERA = fbx_utils.MAT_CONVERT_CAMERA.inverted() +def validate_blend_names(name): + assert(type(name) == bytes) + # Blender typically does not accept names over 63 bytes... + if len(name) > 63: + import hashlib + h = hashlib.sha1(name).hexdigest() + return name[:55].decode('utf-8', 'replace') + "_" + h[:7] + else: + # We use 'replace' even though FBX 'specs' say it should always be utf8, see T53841. + return name.decode('utf-8', 'replace') + + def elem_find_first(elem, id_search, default=None): for fbx_item in elem.elems: if fbx_item.id == id_search: @@ -82,7 +94,7 @@ def elem_find_first_string(elem, id_search): if fbx_item is not None and fbx_item.props: # Do not error on complete empty properties (see T45291). assert(len(fbx_item.props) == 1) assert(fbx_item.props_type[0] == data_types.STRING) - return fbx_item.props[0].decode('utf-8') + return fbx_item.props[0].decode('utf-8', 'replace') return None @@ -124,14 +136,14 @@ def elem_name_ensure_class(elem, clss=...): elem_name, elem_class = elem_split_name_class(elem) if clss is not ...: assert(elem_class == clss) - return elem_name.decode('utf-8') + return validate_blend_names(elem_name) def elem_name_ensure_classes(elem, clss=...): elem_name, elem_class = elem_split_name_class(elem) if clss is not ...: assert(elem_class in clss) - return elem_name.decode('utf-8') + return validate_blend_names(elem_name) def elem_split_name_class_nodeattr(elem): @@ -308,13 +320,14 @@ def blen_read_custom_properties(fbx_obj, blen_obj, settings): # Special case for 3DS Max user properties: assert(fbx_prop.props[1] == b'KString') assert(fbx_prop.props_type[4] == data_types.STRING) - items = fbx_prop.props[4].decode('utf-8') + items = fbx_prop.props[4].decode('utf-8', 'replace') for item in items.split('\r\n'): if item: prop_name, prop_value = item.split('=', 1) - blen_obj[prop_name.strip()] = prop_value.strip() + prop_name = validate_blend_names(prop_name.strip().encode('utf-8')) + blen_obj[prop_name] = prop_value.strip() else: - prop_name = fbx_prop.props[0].decode('utf-8') + prop_name = validate_blend_names(fbx_prop.props[0]) prop_type = fbx_prop.props[1] if prop_type in {b'Vector', b'Vector3D', b'Color', b'ColorRGB'}: assert(fbx_prop.props_type[4:7] == bytes((data_types.FLOAT64,)) * 3) @@ -330,7 +343,7 @@ def blen_read_custom_properties(fbx_obj, blen_obj, settings): blen_obj[prop_name] = fbx_prop.props[4] elif prop_type == b'KString': assert(fbx_prop.props_type[4] == data_types.STRING) - blen_obj[prop_name] = fbx_prop.props[4].decode('utf-8') + blen_obj[prop_name] = fbx_prop.props[4].decode('utf-8', 'replace') elif prop_type in {b'Number', b'double', b'Double'}: assert(fbx_prop.props_type[4] == data_types.FLOAT64) blen_obj[prop_name] = fbx_prop.props[4] @@ -344,13 +357,13 @@ def blen_read_custom_properties(fbx_obj, blen_obj, settings): assert(fbx_prop.props_type[4:6] == bytes((data_types.INT32, data_types.STRING))) val = fbx_prop.props[4] if settings.use_custom_props_enum_as_string and fbx_prop.props[5]: - enum_items = fbx_prop.props[5].decode('utf-8').split('~') + enum_items = fbx_prop.props[5].decode('utf-8', 'replace').split('~') assert(val >= 0 and val < len(enum_items)) blen_obj[prop_name] = enum_items[val] else: blen_obj[prop_name] = val else: - print ("WARNING: User property type '%s' is not supported" % prop_type.decode('utf-8')) + print ("WARNING: User property type '%s' is not supported" % prop_type.decode('utf-8', 'replace')) def blen_read_object_transform_do(transform_data): @@ -544,7 +557,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset): 'Bake' loc/rot/scale into the action, taking any pre_ and post_ matrix into account to transform from fbx into blender space. """ - from bpy.types import Object, PoseBone, ShapeKey, Material + from bpy.types import Object, PoseBone, ShapeKey, Material, Camera from itertools import chain fbx_curves = [] @@ -564,6 +577,8 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset): props = [("diffuse_color", 3, grpname or "Diffuse Color")] elif isinstance(item, ShapeKey): props = [(item.path_from_id("value"), 1, "Key")] + elif isinstance(item, Camera): + props = [(item.path_from_id("lens"), 1, "Camera")] else: # Object or PoseBone: if item.is_bone: bl_obj = item.bl_obj.pose.bones[item.bl_bone] @@ -611,6 +626,17 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset): for fc, v in zip(blen_curves, (value,)): fc.keyframe_points.insert(frame, v, {'NEEDED', 'FAST'}).interpolation = 'LINEAR' + elif isinstance(item, Camera): + for frame, values in blen_read_animations_curves_iter(fbx_curves, anim_offset, 0, fps): + value = 0.0 + for v, (fbxprop, channel, _fbx_acdata) in values: + assert(fbxprop == b'FocalLength') + assert(channel == 0) + value = v + + for fc, v in zip(blen_curves, (value,)): + fc.keyframe_points.insert(frame, v, {'NEEDED', 'FAST'}).interpolation = 'LINEAR' + else: # Object or PoseBone: if item.is_bone: bl_obj = item.bl_obj.pose.bones[item.bl_bone] @@ -673,7 +699,7 @@ def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_o Only the first found action is linked to objects, more complex setups are not handled, it's up to user to reproduce them! """ - from bpy.types import ShapeKey, Material + from bpy.types import ShapeKey, Material, Camera actions = {} for as_uuid, ((fbx_asdata, _blen_data), alayers) in stacks.items(): @@ -685,6 +711,8 @@ def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_o id_data = item elif isinstance(item, ShapeKey): id_data = item.id_data + elif isinstance(item, Camera): + id_data = item else: id_data = item.bl_obj # XXX Ignore rigged mesh animations - those are a nightmare to handle, see note about it in @@ -715,7 +743,7 @@ def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_o def blen_read_geom_layerinfo(fbx_layer): return ( - elem_find_first_string(fbx_layer, b'Name'), + validate_blend_names(elem_find_first_string_as_bytes(fbx_layer, b'Name')), elem_find_first_string_as_bytes(fbx_layer, b'MappingInformationType'), elem_find_first_string_as_bytes(fbx_layer, b'ReferenceInformationType'), ) @@ -1270,18 +1298,17 @@ def blen_read_shape(fbx_tmpl, fbx_sdata, fbx_bcdata, meshes, scene): if me.shape_keys is None: objects[0].shape_key_add(name="Basis", from_mix=False) - objects[0].shape_key_add(name=elem_name_utf8, from_mix=False) + kb = objects[0].shape_key_add(name=elem_name_utf8, from_mix=False) me.shape_keys.use_relative = True # Should already be set as such. - kb = me.shape_keys.key_blocks[elem_name_utf8] for idx, co in vcos: kb.data[idx].co[:] = co kb.value = weight # Add vgroup if necessary. if create_vg: - add_vgroup_to_objects(indices, vgweights, elem_name_utf8, objects) - kb.vertex_group = elem_name_utf8 + vgoups = add_vgroup_to_objects(indices, vgweights, kb.name, objects) + kb.vertex_group = kb.name keyblocks.append(kb) @@ -2825,6 +2852,13 @@ def load(operator, context, filepath="", if keyblocks is None: continue items += [(kb, lnk_prop) for kb in keyblocks] + elif lnk_prop == b'FocalLength': # Camera lens. + from bpy.types import Camera + fbx_item = fbx_table_nodes.get(n_uuid, None) + if fbx_item is None or not isinstance(fbx_item[1], Camera): + continue + cam = fbx_item[1] + items.append((cam, lnk_prop)) elif lnk_prop == b'DiffuseColor': from bpy.types import Material fbx_item = fbx_table_nodes.get(n_uuid, None) @@ -2861,7 +2895,11 @@ def load(operator, context, filepath="", continue # Note this is an infamous simplification of the compound props stuff, # seems to be standard naming but we'll probably have to be smarter to handle more exotic files? - channel = {b'd|X': 0, b'd|Y': 1, b'd|Z': 2, b'd|DeformPercent': 0}.get(acn_ctype.props[3], None) + channel = { + b'd|X': 0, b'd|Y': 1, b'd|Z': 2, + b'd|DeformPercent': 0, + b'd|FocalLength': 0 + }.get(acn_ctype.props[3], None) if channel is None: continue curvenodes[acn_uuid][ac_uuid] = (fbx_acitem, channel) diff --git a/io_scene_fbx/json2fbx.py b/io_scene_fbx/json2fbx.py index dc4c5bcc..579b45a7 100755 --- a/io_scene_fbx/json2fbx.py +++ b/io_scene_fbx/json2fbx.py @@ -108,9 +108,9 @@ def parse_json_rec(fbx_root, json_node): elif dt == "d": e.add_float64_array(d) elif dt == "b": - e.add_byte_array(d) - elif dt == "c": e.add_bool_array(d) + elif dt == "c": + e.add_byte_array(d) if name == "FBXVersion": assert(data_types == "I") diff --git a/io_scene_obj/import_obj.py b/io_scene_obj/import_obj.py index 7ca22160..f3e16fcf 100644 --- a/io_scene_obj/import_obj.py +++ b/io_scene_obj/import_obj.py @@ -109,7 +109,7 @@ def create_materials(filepath, relpath, curr_token = [] for token in img_data[:-1]: - if token.startswith(b'-'): + if token.startswith(b'-') and token[1:].isalpha(): if curr_token: map_options[curr_token[0]] = curr_token[1:] curr_token[:] = [] diff --git a/materials_library_vx/__init__.py b/materials_library_vx/__init__.py index 9a8b47f9..07131862 100644 --- a/materials_library_vx/__init__.py +++ b/materials_library_vx/__init__.py @@ -21,7 +21,7 @@ bl_info = { "name": "Material Library", "author": "Mackraken (mackraken2023@hotmail.com)", - "version": (0, 5, 7), + "version": (0, 5, 8), "blender": (2, 7, 8), "api": 60995, "location": "Properties > Material", @@ -114,7 +114,7 @@ def check_index(collection, index): def send_command(cmd, output="sendmat.py"): bin = winpath(bpy.app.binary_path) - scriptpath = winpath(os.path.join(matlib_path, output)) + scriptpath = winpath(os.path.join(bpy.app.tempdir, output)) with open(scriptpath, "w") as f: f.write(cmd) @@ -484,11 +484,8 @@ if mat: # self.current_library.materials[self.mat_index].category = cat #remove mat from any category else: - matnode = xml.find("material", mat.name, lib) - if matnode: - xml.deleteNode(matnode) mat.category = "" - self.current_library.materials[self.mat_index].category = "" + self.all_materials[self.mat_index].category = "" else: return "WARNING", "Select a material" @@ -741,8 +738,7 @@ bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)''' # Scene.matlib = PointerProperty(type = matlibProperties) ### MENUS -class matlibLibsMenu(Menu): - bl_idname = "matlib.libs_menu" +class MATLIB_MT_LibsMenu(Menu): bl_label = "Libraries Menu" def draw(self, context): @@ -752,8 +748,7 @@ class matlibLibsMenu(Menu): for i, lib in enumerate(libs): layout.operator("matlib.operator", text=lib.shortname).cmd="lib"+str(i) -class matlibCatsMenu(Menu): - bl_idname = "matlib.cats_menu" +class MATLIB_MT_CatsMenu(Menu): bl_label = "Categories Menu" def draw(self, context): @@ -779,7 +774,7 @@ class matlibCatsMenu(Menu): -class MatlibAdd(Operator): +class MATLIB_OT_add(Operator): """Add active material to library""" bl_idname = "matlib.add" bl_label = "Add active material" @@ -797,7 +792,7 @@ class MatlibAdd(Operator): self.report({success[0]}, success[1]) return {'FINISHED'} -class MatlibRemove(Operator): +class MATLIB_OT_remove(Operator): """Remove material from library""" bl_idname = "matlib.remove" bl_label = "Remove material from library" @@ -815,7 +810,7 @@ class MatlibRemove(Operator): self.report({success[0]}, success[1]) return {'FINISHED'} -class MatlibReload(Operator): +class MATLIB_OT_remove(Operator): """Reload library""" bl_idname = "matlib.reload" bl_label = "Reload library" @@ -836,7 +831,7 @@ class MatlibReload(Operator): return {'FINISHED'} -class MatlibApply(Operator): +class MATLIB_OT_apply(Operator): """Apply selected material""" bl_idname = "matlib.apply" bl_label = "Apply material" @@ -858,7 +853,7 @@ class MatlibApply(Operator): return {'FINISHED'} -class MatlibPreview(Operator): +class MATLIB_OT_preview(Operator): """Preview selected material""" bl_idname = "matlib.preview" bl_label = "Preview selected material" @@ -880,7 +875,7 @@ class MatlibPreview(Operator): return {'FINISHED'} -class MatlibFlush(Operator): +class MATLIB_OT_flush(Operator): """Flush unused materials""" bl_idname = "matlib.flush" bl_label = "Flush unused materials" @@ -917,7 +912,7 @@ class MatlibFlush(Operator): return {'FINISHED'} -class matlibOperator(Operator): +class MATLIB_OT_operator(Operator): """Add, Remove, Reload, Apply, Preview, Clean Material""" bl_label = "New" bl_idname = "matlib.operator" @@ -1118,7 +1113,7 @@ for mat in mats: return {'FINISHED'} -class matlibvxPanel(Panel): +class MATLIB_PT_vxPanel(Panel): bl_label = "Material Library VX" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -1144,7 +1139,7 @@ class matlibvxPanel(Panel): else: text = "Select a Library" - row.menu("matlib.libs_menu",text=text) + row.menu("MATLIB_MT_LibsMenu",text=text) row.operator("matlib.operator", icon="ZOOMIN", text="").cmd = "LIBRARY_ADD" if matlib.active_material: row.label(matlib.active_material.category) @@ -1175,7 +1170,7 @@ class matlibvxPanel(Panel): row = layout.row(align=True) text = "All" if matlib.current_category: text = matlib.current_category - row.menu("matlib.cats_menu",text=text) + row.menu("MATLIB_MT_CatsMenu",text=text) row.prop(matlib, "filter", icon="FILTER", text="") row.operator("matlib.operator", icon="FILE_PARENT", text="").cmd="FILTER_SET" row.operator("matlib.operator", icon="ZOOMIN", text="").cmd="FILTER_ADD" @@ -1198,7 +1193,7 @@ class matlibvxPanel(Panel): # else: # row.label("Library not found!.") -#classes = [matlibvxPanel, matlibOperator, matlibLibsMenu, matlibCatsMenu] +#classes = [MATLIB_PT_vxPanel, MATLIB_OT_operator, MATLIB_MT_LibsMenu, MATLIB_MT_CatsMenu] #print(bpy.context.scene) diff --git a/materials_utils/__init__.py b/materials_utils/__init__.py index c768d770..ea85a104 100644 --- a/materials_utils/__init__.py +++ b/materials_utils/__init__.py @@ -26,7 +26,7 @@ bl_info = { "name": "Materials Utils Specials", "author": "Community", - "version": (1, 0, 3), + "version": (1, 0, 5), "blender": (2, 77, 0), "location": "Materials Properties Specials > Shift Q", "description": "Materials Utils and Convertors", @@ -34,7 +34,7 @@ bl_info = { "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" "Scripts/3D_interaction/Materials_Utils", "category": "Material" - } +} if "bpy" in locals(): import importlib @@ -51,27 +51,35 @@ else: import bpy import os from os import ( - path as os_path, - access as os_access, - remove as os_remove, - ) + path as os_path, + access as os_access, + remove as os_remove, +) from bpy.props import ( - StringProperty, - BoolProperty, - EnumProperty, - PointerProperty, - ) + BoolProperty, + CollectionProperty, + EnumProperty, + IntProperty, + StringProperty, + PointerProperty, +) from bpy.types import ( - Menu, - Operator, - Panel, - AddonPreferences, - PropertyGroup, - ) + AddonPreferences, + Menu, + Operator, + Panel, + PropertyGroup, + UIList, +) from .warning_messages_utils import ( - warning_messages, - c_data_has_materials, - ) + warning_messages, + c_data_has_materials, + c_obj_data_has_materials, +) + +# Globals +UNDO_MESSAGE = "*Only Undo is available*" +COLUMN_SPLIT = 20 # Functions @@ -146,7 +154,7 @@ def replace_material(m1, m2, all_objects=False, update_selection=False, operator if m.material == matorg: m.material = matrep # don't break the loop as the material can be - # ref'd more than once + # referenced more than once # Indicate which objects were affected if update_selection: @@ -169,7 +177,7 @@ def select_material_by_name(find_mat_name): if find_mat is None: return - # check for editmode + # check for edit mode editmode = False scn = bpy.context.scene @@ -200,7 +208,7 @@ def select_material_by_name(find_mat_name): else: ob.select = False else: - # it's editmode, so select the polygons + # it's edit mode, so select the polygons ob = actob ms = ob.material_slots @@ -227,7 +235,7 @@ def select_material_by_name(find_mat_name): def mat_to_texface(operator=None): # assigns the first image in each material to the polygons in the active - # uvlayer for all selected objects + # uv layer for all selected objects # check for editmode editmode = False @@ -282,7 +290,7 @@ def mat_to_texface(operator=None): bpy.ops.mesh.uv_texture_add() scn.objects.active = actob - # get active uvlayer + # get active uv layer for t in me.uv_textures: if t.active: uvtex = t.data @@ -316,7 +324,7 @@ def assignmatslots(ob, matlist): scn.objects.active = ob for s in ob.material_slots: - bpy.ops.object.material_slot_remove() + remove_material_slot() # re-add them and assign material if matlist: @@ -330,7 +338,7 @@ def assignmatslots(ob, matlist): # to face indices, mat tries to get an '' as mat index pass - # restore active object: + # restore active object scn.objects.active = ob_active @@ -352,65 +360,97 @@ def cleanmatslots(operator=None): objs = bpy.context.selected_editable_objects # collect all object names for warning_messages message_a = [] - # Flag if there are non MESH objects selected - mixed_obj = False + # Flags if there are non MESH objects selected + mixed_obj, mixed_obj_slot = False, False for ob in objs: - if ob.type == 'MESH': - mats = ob.material_slots.keys() - - # if mats is empty then mats[faceindex] will be out of range - if mats: - # check the polygons on the mesh to build a list of used materials - usedMatIndex = [] # we'll store used materials indices here - faceMats = [] - me = ob.data - for f in me.polygons: - # get the material index for this face... - faceindex = f.material_index - - # indices will be lost: Store face mat use by name - currentfacemat = mats[faceindex] - faceMats.append(currentfacemat) - - # check if index is already listed as used or not - found = False - for m in usedMatIndex: - if m == faceindex: - found = True - # break - - if found is False: - # add this index to the list - usedMatIndex.append(faceindex) - - # re-assign the used mats to the mesh and leave out the unused - ml = [] - mnames = [] - for u in usedMatIndex: - ml.append(mats[u]) - # we'll need a list of names to get the face indices... - mnames.append(mats[u]) - - assignmatslots(ob, ml) - - # restore face indices: - i = 0 - for f in me.polygons: - matindex = mnames.index(faceMats[i]) - f.material_index = matindex - i += 1 - else: - message_a.append(getattr(ob, "name", "NO NAME")) - continue - else: + if ob.type != 'MESH': + mat_empty = [] message_a.append(getattr(ob, "name", "NO NAME")) if mixed_obj is False: mixed_obj = True + + # at least try to remove empty material slots + if ob.type in {'CURVE', 'SURFACE', 'FONT', 'META'}: + mats = ob.material_slots + mat_empty = [i for i, slot in enumerate(mats) if not slot.material] + + if not mat_empty: + continue + + if mixed_obj_slot is False: + mixed_obj_slot = True + + # Ctx - copy the context for operator override + Ctx = bpy.context.copy() + # for this operator it needs only the active object replaced + Ctx['object'] = ob + + for index in mat_empty: + try: + ob.active_material_index = index + bpy.ops.object.material_slot_remove(Ctx) + except: + continue + continue + + mats = ob.material_slots.keys() + maxindex = len(mats) - 1 # indices start from zero + + # if mats is empty then mats[faceindex] will be out of range + if not mats: + message_a.append(getattr(ob, "name", "NO NAME")) continue + # check the polygons on the mesh to build a list of used materials + usedMatIndex = [] # we'll store used materials indices here + faceMats = [] + badIndices = set() # collect face indices that are out material slot's range + me = ob.data + for f in me.polygons: + # get the material index for this face... + faceindex = f.material_index + # check if the mats[faceindex] is not out of range + if faceindex > maxindex: + badIndices.add(faceindex) + continue + + # indices will be lost: Store face mat use by name + currentfacemat = mats[faceindex] + faceMats.append(currentfacemat) + + # check if index is already listed as used or not + found = False + for m in usedMatIndex: + if m == faceindex: + found = True + + if found is False: + # add this index to the list + usedMatIndex.append(faceindex) + + # re-assign the used mats to the mesh and leave out the unused + ml = [] + mnames = [] + for u in usedMatIndex: + if u not in badIndices: + ml.append(mats[u]) + # we'll need a list of names to get the face indices... + mnames.append(mats[u]) + + assignmatslots(ob, ml) + + # restore face indices: + i = 0 + for f in me.polygons: + if i not in badIndices: + matindex = mnames.index(faceMats[i]) + f.material_index = matindex + i += 1 + if message_a and operator: - warn_mess = ('C_OB_MIX_NO_MAT' if mixed_obj is True else 'C_OB_NO_MAT') + warn_s = 'C_OB_MIX_NO_MAT' if mixed_obj is True else 'C_OB_NO_MAT' + warn_mess = 'C_OB_MIX_SLOT_MAT' if mixed_obj_slot else warn_s warning_messages(operator, warn_mess, message_a) if actob: @@ -426,12 +466,11 @@ def cleanmatslots(operator=None): def assign_mat_mesh_edit(matname="Default", operator=None): actob = bpy.context.active_object found = False - for m in bpy.data.materials: - if m.name == matname: - target = m - found = True - break - if not found: + + # check if material exists, if it doesn't then create it + target = bpy.data.materials.get(matname) + + if not target: target = bpy.data.materials.new(matname) if (actob.type in {'MESH'} and actob.mode in {'EDIT'}): @@ -447,8 +486,8 @@ def assign_mat_mesh_edit(matname="Default", operator=None): break i += 1 + # the material is not attached to the object if not found: - # the material is not attached to the object actob.data.materials.append(target) # is selected ? @@ -472,7 +511,7 @@ def assign_mat_mesh_edit(matname="Default", operator=None): def assign_mat(matname="Default", operator=None): - # get active object so we can restore it later + # get the active object so we can restore it later actob = bpy.context.active_object # is active object selected ? @@ -480,19 +519,15 @@ def assign_mat(matname="Default", operator=None): actob.select = True # check if material exists, if it doesn't then create it - found = False - for m in bpy.data.materials: - if m.name == matname: - target = m - found = True - break + target = bpy.data.materials.get(matname) - if not found: + if not target: target = bpy.data.materials.new(matname) - # if objectmode then set all polygons + # if object mode then set all polygons editmode = False allpolygons = True + if actob.mode == 'EDIT': editmode = True allpolygons = False @@ -596,7 +631,7 @@ def check_texture(img, mat): def texface_to_mat(operator=None): - # editmode check here! + # edit mode check here! editmode = False ob = bpy.context.object if ob.mode == 'EDIT': @@ -604,7 +639,6 @@ def texface_to_mat(operator=None): bpy.ops.object.mode_set() for ob in bpy.context.selected_editable_objects: - faceindex = [] unique_images = [] # collect object names for warning messages @@ -668,27 +702,30 @@ def remove_materials(operator=None, setting="SLOT"): actob = bpy.context.active_object actob_name = getattr(actob, "name", "NO NAME") - if actob: - if not included_object_types(actob.type): - if operator: - warning_messages(operator, 'OB_CANT_MAT', actob_name) - else: - if (hasattr(actob.data, "materials") and - len(actob.data.materials) > 0): - if setting == "SLOT": - bpy.ops.object.material_slot_remove() - elif setting == "ALL": - for mat in actob.data.materials: - try: - bpy.ops.object.material_slot_remove() - except: - pass - - if operator: - warn_mess = ('R_ACT_MAT_ALL' if setting == "ALL" else 'R_ACT_MAT') - warning_messages(operator, warn_mess, actob_name) - elif operator: - warning_messages(operator, 'R_OB_NO_MAT', actob_name) + if not actob: + return + + if not included_object_types(actob.type): + if operator: + warning_messages(operator, 'OB_CANT_MAT', actob_name) + return + + if (hasattr(actob.data, "materials") and len(actob.data.materials) > 0): + if setting == "SLOT": + remove_material_slot() + elif setting == "ALL": + for mat in actob.data.materials: + try: + remove_material_slot() + except: + pass + + if operator: + warn_mess = 'R_ACT_MAT_ALL' if setting == "ALL" else 'R_ACT_MAT' + warning_messages(operator, warn_mess, actob_name) + + elif operator: + warning_messages(operator, 'R_OB_NO_MAT', actob_name) def remove_materials_all(operator=None): @@ -700,11 +737,10 @@ def remove_materials_all(operator=None): if not included_object_types(ob.type): continue else: - # code from blender stackexchange (by CoDEmanX) + # code from blender stack exchange (by CoDEmanX) ob.active_material_index = 0 - if (hasattr(ob.data, "materials") and - len(ob.material_slots) >= 1): + if (hasattr(ob.data, "materials") and len(ob.material_slots) >= 1): mat_count = True # Ctx - copy the context for operator override @@ -740,9 +776,10 @@ class VIEW3D_OT_show_mat_preview(Operator): @classmethod def poll(cls, context): - return (context.active_object is not None and - context.object.active_material is not None and - included_object_types(context.object.type)) + obj = context.active_object + return (obj is not None and + obj.active_material is not None and + included_object_types(obj.type)) def invoke(self, context, event): self.is_not_undo = True @@ -753,89 +790,107 @@ class VIEW3D_OT_show_mat_preview(Operator): ob = context.active_object prw_size = size_preview() - if self.is_not_undo is True: - if ob and hasattr(ob, "active_material"): - mat = ob.active_material - is_opaque = (True if (ob and hasattr(ob, "show_transparent") and - ob.show_transparent is True) - else False) - is_opaque_bi = (True if (mat and hasattr(mat, "use_transparency") and - mat.use_transparency is True) - else False) - is_mesh = (True if ob.type == 'MESH' else False) - - if size_type_is_preview(): - layout.template_ID_preview(ob, "active_material", new="material.new", - rows=prw_size['Width'], cols=prw_size['Height']) - else: - layout.template_ID(ob, "active_material", new="material.new") - layout.separator() - - if c_render_engine("Both"): - layout.prop(mat, "use_nodes", icon='NODETREE') - - if c_need_of_viewport_colors(): - color_txt = ("Viewport Color:" if c_render_engine("Cycles") else "Diffuse") - spec_txt = ("Viewport Specular:" if c_render_engine("Cycles") else "Specular") - col = layout.column(align=True) - col.label(color_txt) - col.prop(mat, "diffuse_color", text="") - if c_render_engine("BI"): - # Blender Render - col.prop(mat, "diffuse_intensity", text="Intensity") - col.separator() - - col.label(spec_txt) - col.prop(mat, "specular_color", text="") - col.prop(mat, "specular_hardness") - - if (c_render_engine("BI") and not c_context_use_nodes()): - # Blender Render - col.separator() - col.prop(mat, "use_transparency") - col.separator() - if is_opaque_bi: - col.prop(mat, "transparency_method", text="") - col.separator() - col.prop(mat, "alpha") - elif (c_render_engine("Cycles") and is_mesh): - # Cycles - col.separator() - col.prop(ob, "show_transparent", text="Transparency") - if is_opaque: - col.separator() - col.prop(mat, "alpha") - layout.separator() - else: - other_render = ("*Unavailable with this Renderer*" if not c_render_engine("Both") - else "*Unavailable in this Context*") - no_col_label = ("*Only available in Solid Shading*" if c_render_engine("Cycles") - else other_render) - layout.label(no_col_label, icon="INFO") + if self.is_not_undo is False: + layout.label(text=UNDO_MESSAGE, icon="INFO") + return + + if not (ob and hasattr(ob, "active_material")): + layout.label(text="No Active Object or Active Material", icon="INFO") + return + + mat = ob.active_material + is_opaque = ( + True if (ob and hasattr(ob, "show_transparent") and + ob.show_transparent is True) else False + ) + is_opaque_bi = ( + True if (mat and hasattr(mat, "use_transparency") and + mat.use_transparency is True) else False + ) + is_mesh = True if ob.type == 'MESH' else False + + if size_type_is_preview(): + layout.template_ID_preview(ob, "active_material", new="material.new", + rows=prw_size['Width'], cols=prw_size['Height']) else: - layout.label(text="**Only Undo is available**", icon="INFO") + layout.template_ID(ob, "active_material", new="material.new") + layout.separator() + + if c_render_engine("Both"): + layout.prop(mat, "use_nodes", icon='NODETREE') + + if not c_need_of_viewport_colors(): + other_render = ( + "*Unavailable with this Renderer*" if not c_render_engine("Both") else + "*Unavailable in this Context*" + ) + no_col_label = ( + "*Only available in Solid Shading*" if c_render_engine("Cycles") else + other_render + ) + layout.label(no_col_label, icon="INFO") + return + + color_txt = "Viewport Color:" if c_render_engine("Cycles") else "Diffuse" + spec_txt = "Viewport Specular:" if c_render_engine("Cycles") else "Specular" + col = layout.column(align=True) + col.label(color_txt) + col.prop(mat, "diffuse_color", text="") + + if c_render_engine("BI"): + # Blender Render + col.prop(mat, "diffuse_intensity", text="Intensity") + col.separator() + + col.label(spec_txt) + col.prop(mat, "specular_color", text="") + col.prop(mat, "specular_hardness") + + if (c_render_engine("BI") and not c_context_use_nodes()): + # Blender Render + col.separator() + col.prop(mat, "use_transparency") + col.separator() + if is_opaque_bi: + col.prop(mat, "transparency_method", text="") + col.separator() + col.prop(mat, "alpha") + elif (c_render_engine("Cycles") and is_mesh): + # Cycles + col.separator() + col.prop(ob, "show_transparent", text="Transparency") + if is_opaque: + col.separator() + col.prop(mat, "alpha") + col.separator() + col.label("Viewport Alpha:") + col.prop(mat.game_settings, "alpha_blend", text="") + layout.separator() def check(self, context): return self.is_not_undo def execute(self, context): self.is_not_undo = False + return {'FINISHED'} class VIEW3D_OT_copy_material_to_selected(Operator): bl_idname = "view3d.copy_material_to_selected" bl_label = "Copy Materials to others" - bl_description = ("Copy Material From Active to Selected objects \n" + bl_description = ("Copy Material From Active to Selected objects\n" + "In case of multiple materials, only the first slot is assigned\n" "Works on Object's Data linked Materials") bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): + obj = context.active_object return (c_data_has_materials() and - context.active_object is not None and - included_object_types(context.active_object.type) and - context.object.active_material is not None and + obj is not None and + included_object_types(obj.type) and + obj.active_material is not None and context.selected_editable_objects) def execute(self, context): @@ -853,6 +908,7 @@ class VIEW3D_OT_copy_material_to_selected(Operator): return {'CANCELLED'} warning_messages(self, warn_mess) + return {'FINISHED'} @@ -872,13 +928,14 @@ class VIEW3D_OT_texface_to_material(Operator): return context.window_manager.invoke_confirm(self, event) def execute(self, context): - if context.selected_editable_objects: - texface_to_mat(self) - return {'FINISHED'} - else: + if not context.selected_editable_objects: warning_messages(self, 'TEX_MAT_NO_SL') return {'CANCELLED'} + texface_to_mat(self) + + return {'FINISHED'} + class VIEW3D_OT_set_new_material_name(Operator): bl_idname = "view3d.set_new_material_name" @@ -901,6 +958,7 @@ class VIEW3D_OT_set_new_material_name(Operator): layout.prop(scene, "use_tweak") def execute(self, context): + return {'FINISHED'} @@ -908,34 +966,61 @@ class VIEW3D_OT_assign_material(Operator): bl_idname = "view3d.assign_material" bl_label = "Assign Material" bl_description = "Assign a material to the selection" + bl_property = "matname" bl_options = {'REGISTER', 'UNDO'} is_existing = BoolProperty( name="Is it a new Material", options={'HIDDEN'}, default=True, - ) + ) matname = StringProperty( name="Material Name", description="Name of the Material to Assign", options={'HIDDEN'}, default="Material_New", maxlen=128, - ) + ) + is_not_undo = False # prevent drawing props on undo @classmethod def poll(cls, context): return context.active_object is not None def invoke(self, context, event): - return self.execute(context) + self.is_not_undo = True + + if not self.is_existing or use_mat_menu_type() not in {'POPUP'}: + return self.execute(context) + + materials_lists_fill_names(context, refresh=True, is_object=False) + return context.window_manager.invoke_props_dialog(self, width=400, height=200) + + def check(self, context): + return self.is_not_undo + + def draw(self, context): + draw_ui_list_popups(self, context, obj_data=False) def execute(self, context): actob = context.active_object - mn = self.matname scene = context.scene.mat_specials + mn = self.matname tweak = scene.use_tweak + if use_mat_menu_type() == 'POPUP' and self.is_existing: + mats_col = context.scene.mat_specials_mats + len_mats = len(mats_col) + mat_index = scene.index_mat + + if not (len_mats > 0 and mat_index is not None and mat_index <= len_mats): + self.report({'WARNING'}, + "No Materials in the Scene. Please use the Add New option") + return {"CANCELLED"} + + mats_up = mats_col[mat_index].name + mn = mats_up + if not self.is_existing: new_name = check_mat_name_unique(scene.set_material_name) mn = new_name @@ -951,6 +1036,8 @@ class VIEW3D_OT_assign_material(Operator): mat_to_texface() self.is_not_undo = False + activate_mat_slot(actob, mn) + if tweak and not self.is_existing: try: bpy.ops.view3d.show_mat_preview('INVOKE_DEFAULT') @@ -976,6 +1063,7 @@ class VIEW3D_OT_clean_material_slots(Operator): def execute(self, context): cleanmatslots(self) + return {'FINISHED'} @@ -993,18 +1081,19 @@ class VIEW3D_OT_material_to_texface(Operator): context.active_object is not None) def execute(self, context): - if context.selected_editable_objects: - mat_to_texface(self) - return {'FINISHED'} - else: + if not context.selected_editable_objects: warning_messages(self, "MAT_TEX_NO_SL") return {'CANCELLED'} + mat_to_texface(self) + + return {'FINISHED'} + class VIEW3D_OT_material_remove_slot(Operator): bl_idname = "view3d.material_remove_slot" bl_label = "Remove Active Slot (Active Object)" - bl_description = ("Remove active material slot from active object\n" + bl_description = ("Remove active material slot from active object \n" "Can't be used in Edit Mode") bl_options = {'REGISTER', 'UNDO'} @@ -1013,21 +1102,22 @@ class VIEW3D_OT_material_remove_slot(Operator): # materials can't be removed in Edit mode return (c_data_has_materials() and context.active_object is not None and - not context.object.mode == 'EDIT') + not context.active_object.mode == 'EDIT') def execute(self, context): - if context.selected_editable_objects: - remove_materials(self, "SLOT") - return {'FINISHED'} - else: + if not context.selected_editable_objects: warning_messages(self, 'R_NO_SL_MAT') return {'CANCELLED'} + remove_materials(self, "SLOT") + + return {'FINISHED'} + class VIEW3D_OT_material_remove_object(Operator): bl_idname = "view3d.material_remove_object" bl_label = "Remove all Slots (Active Object)" - bl_description = ("Remove all material slots from active object\n" + bl_description = ("Remove all material slots from active object \n" "Can't be used in Edit Mode") bl_options = {'REGISTER', 'UNDO'} @@ -1036,16 +1126,17 @@ class VIEW3D_OT_material_remove_object(Operator): # materials can't be removed in Edit mode return (c_data_has_materials() and context.active_object is not None and - not context.object.mode == 'EDIT') + not context.active_object.mode == 'EDIT') def execute(self, context): - if context.selected_editable_objects: - remove_materials(self, "ALL") - return {'FINISHED'} - else: + if not context.selected_editable_objects: warning_messages(self, 'R_NO_SL_MAT') return {'CANCELLED'} + remove_materials(self, "ALL") + + return {'FINISHED'} + class VIEW3D_OT_material_remove_all(Operator): bl_idname = "view3d.material_remove_all" @@ -1059,19 +1150,20 @@ class VIEW3D_OT_material_remove_all(Operator): # materials can't be removed in Edit mode return (c_data_has_materials() and context.active_object is not None and - not context.object.mode == 'EDIT') + not context.active_object.mode == 'EDIT') def invoke(self, context, event): return context.window_manager.invoke_confirm(self, event) def execute(self, context): - if context.selected_editable_objects: - remove_materials_all(self) - return {'FINISHED'} - else: + if not context.selected_editable_objects: warning_messages(self, 'R_NO_SL_MAT') return {'CANCELLED'} + remove_materials_all(self) + + return {'FINISHED'} + class VIEW3D_OT_select_material_by_name(Operator): bl_idname = "view3d.select_material_by_name" @@ -1080,20 +1172,59 @@ class VIEW3D_OT_select_material_by_name(Operator): bl_options = {'REGISTER', 'UNDO'} matname = StringProperty( - name='Material Name', - description='Name of Material to Select', - maxlen=63, - ) + name="Material Name", + description="Name of Material to Select", + maxlen=63, + ) + is_not_undo = False + is_edit = False @classmethod def poll(cls, context): + obj = context.active_object return (c_data_has_materials() and - context.active_object is not None) + obj is not None and obj.mode in {"OBJECT", "EDIT"}) + + def invoke(self, context, event): + self.is_not_undo = True + + if use_mat_menu_type() not in {'POPUP'}: + return self.execute(context) + + obj = context.active_object + self.is_edit = bool(obj.mode == 'EDIT') + materials_lists_fill_names(context, refresh=True, is_object=self.is_edit) + + return context.window_manager.invoke_props_dialog(self, width=400, height=200) + + def check(self, context): + return self.is_not_undo + + def draw(self, context): + draw_ui_list_popups(self, context, obj_data=self.is_edit) def execute(self, context): - mn = self.matname + if use_mat_menu_type() == 'POPUP': + mats_col = context.scene.mat_specials_mats + scene = context.scene.mat_specials + len_mats = len(mats_col) + mat_index = scene.index_mat + + if not (len_mats > 0 and mat_index is not None and mat_index <= len_mats): + self.report({'WARNING'}, + "No materials found. Operation Cancelled") + return {"CANCELLED"} + + mats_up = mats_col[mat_index].name + mn = mats_up + else: + mn = self.matname + select_material_by_name(mn) - warning_messages(self, 'SL_MAT_BY_NAME', mn) + message = 'SL_MAT_EDIT_BY_NAME' if self.is_edit else 'SL_MAT_BY_NAME' + warning_messages(self, message, mn) + self.is_not_undo = False + return {'FINISHED'} @@ -1104,25 +1235,26 @@ class VIEW3D_OT_replace_material(Operator): bl_options = {'REGISTER', 'UNDO'} matorg = StringProperty( - name="Original", - description="Material to replace", - maxlen=63, - ) + name="Original", + description="Material to replace", + maxlen=63, + ) matrep = StringProperty( - name="Replacement", - description="Replacement material", - maxlen=63, - ) + name="Replacement", + description="Replacement material", + maxlen=63, + ) all_objects = BoolProperty( - name="All objects", - description="Replace for all objects in this blend file", - default=True, - ) + name="All objects", + description="If enabled, replace for all objects in this blend file\n" + "If disabled, only selected objects will be affected", + default=False, + ) update_selection = BoolProperty( - name="Update Selection", - description="Select affected objects and deselect unaffected", - default=True, - ) + name="Update Selection", + description="Select affected objects and deselect unaffected", + default=True, + ) @classmethod def poll(cls, context): @@ -1139,9 +1271,12 @@ class VIEW3D_OT_replace_material(Operator): return context.window_manager.invoke_props_dialog(self) def execute(self, context): - replace_material(self.matorg, self.matrep, self.all_objects, - self.update_selection, self) + replace_material( + self.matorg, self.matrep, self.all_objects, + self.update_selection, self + ) self.matorg, self.matrep = "", "" + return {'FINISHED'} @@ -1152,22 +1287,22 @@ class VIEW3D_OT_fake_user_set(Operator): bl_options = {'REGISTER', 'UNDO'} fake_user = EnumProperty( - name="Fake User", - description="Turn fake user on or off", - items=(('ON', "On", "Enable fake user"), ('OFF', "Off", "Disable fake user")), - default='ON', - ) + name="Fake User", + description="Turn fake user on or off", + items=(('ON', "On", "Enable fake user"), ('OFF', "Off", "Disable fake user")), + default='ON', + ) materials = EnumProperty( - name="Materials", - description="Chose what objects and materials to affect", - items=(('ACTIVE', "Active object", "Materials of active object only"), - ('SELECTED', "Selected objects", "Materials of selected objects"), - ('SCENE', "Scene objects", "Materials of objects in current scene"), - ('USED', "Used", "All materials used by objects"), - ('UNUSED', "Unused", "Currently unused materials"), - ('ALL', "All", "All materials in this blend file")), - default='UNUSED', - ) + name="Materials", + description="Chose what objects and materials to affect", + items=(('ACTIVE', "Active object", "Materials of active object only"), + ('SELECTED', "Selected objects", "Materials of selected objects"), + ('SCENE', "Scene objects", "Materials of objects in current scene"), + ('USED', "Used", "All materials used by objects"), + ('UNUSED', "Unused", "Currently unused materials"), + ('ALL', "All", "All materials in this blend file")), + default='UNUSED', + ) @classmethod def poll(cls, context): @@ -1183,6 +1318,7 @@ class VIEW3D_OT_fake_user_set(Operator): def execute(self, context): fake_user_set(self.fake_user, self.materials, self) + return {'FINISHED'} @@ -1196,15 +1332,15 @@ class MATERIAL_OT_set_transparent_back_side(Operator): @classmethod def poll(cls, context): obj = context.active_object - if (not obj): + if not obj: return False mat = obj.active_material - if (not mat): + if not mat: return False - if (mat.node_tree): + if mat.node_tree: if (len(mat.node_tree.nodes) == 0): return True - if (not mat.use_nodes): + if not mat.use_nodes: return True return False @@ -1246,7 +1382,7 @@ class MATERIAL_OT_move_slot_top(Operator): @classmethod def poll(cls, context): obj = context.active_object - if (not obj): + if not obj: return False if (len(obj.material_slots) <= 2): return False @@ -1276,7 +1412,7 @@ class MATERIAL_OT_move_slot_bottom(Operator): @classmethod def poll(cls, context): obj = context.active_object - if (not obj): + if not obj: return False if (len(obj.material_slots) <= 2): return False @@ -1306,15 +1442,14 @@ class MATERIAL_OT_link_to_base_names(Operator): bl_options = {'REGISTER', 'UNDO'} mat_keep = StringProperty( - name="Material to keep", - default="", - ) + name="Material to keep", + default="", + ) is_auto = BoolProperty( - name="Auto Rename/Replace", - description=("Automatically Replace names " - "by stripping numerical suffix"), - default=False, - ) + name="Auto Rename/Replace", + description="Automatically Replace names by stripping numerical suffix", + default=False, + ) mat_error = [] # collect mat for warning messages is_not_undo = False # prevent drawing props on undo check_no_name = True # check if no name is passed @@ -1325,16 +1460,18 @@ class MATERIAL_OT_link_to_base_names(Operator): def draw(self, context): layout = self.layout - if self.is_not_undo is True: - boxee = layout.box() - boxee.prop_search(self, "mat_keep", bpy.data, "materials") - boxee.enabled = not self.is_auto - layout.separator() - - boxs = layout.box() - boxs.prop(self, "is_auto", text="Auto Rename/Replace", icon="SYNTAX_ON") - else: - layout.label(text="**Only Undo is available**", icon="INFO") + + if self.is_not_undo is False: + layout.label(text=UNDO_MESSAGE, icon="INFO") + return + + boxee = layout.box() + boxee.prop_search(self, "mat_keep", bpy.data, "materials") + boxee.enabled = not self.is_auto + layout.separator() + + boxs = layout.box() + boxs.prop(self, "is_auto", text="Auto Rename/Replace", icon="SYNTAX_ON") def invoke(self, context, event): self.is_not_undo = True @@ -1430,13 +1567,14 @@ class MATERIAL_OT_link_to_base_names(Operator): warning_messages(self, 'MAT_LINK_ERROR', self.mat_error, 'MAT') self.is_not_undo = False + return {'FINISHED'} class MATERIAL_OT_check_converter_path(Operator): bl_idname = "material.check_converter_path" bl_label = "Check Converters images/data save path" - bl_description = "Check if the given path is writeable (has OS writing privileges)" + bl_description = "Check if the given path is writable (has OS writing privileges)" bl_options = {'REGISTER', 'INTERNAL'} def check_valid_path(self, context): @@ -1447,24 +1585,24 @@ class MATERIAL_OT_check_converter_path(Operator): warning_messages(self, "DIR_PATH_EMPTY", override=True) return False - if os_path.exists(paths): - if os_access(paths, os.W_OK | os.X_OK): - try: - path_test = os_path.join(paths, "XYfoobartestXY.txt") - with open(path_test, 'w') as f: - f.closed - os_remove(path_test) - return True - except (OSError, IOError): - warning_messages(self, 'DIR_PATH_W_ERROR', override=True) - return False - else: - warning_messages(self, 'DIR_PATH_A_ERROR', override=True) - return False - else: + if not os_path.exists(paths): warning_messages(self, 'DIR_PATH_N_ERROR', override=True) return False + if not os_access(paths, os.W_OK | os.X_OK): + warning_messages(self, 'DIR_PATH_A_ERROR', override=True) + return False + + try: + path_test = os_path.join(paths, "XYfoobartestXY.txt") + with open(path_test, 'w') as f: + f.closed + os_remove(path_test) + return True + except (OSError, IOError): + warning_messages(self, 'DIR_PATH_W_ERROR', override=True) + return False + return True def execute(self, context): @@ -1476,6 +1614,67 @@ class MATERIAL_OT_check_converter_path(Operator): return {'FINISHED'} +# Material selections pop-up + +class VIEW3D_UL_assign_material_popup_ui(UIList): + + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + self.use_filter_show = True + + col = layout.column(align=True) + col.alignment = "LEFT" + mat = bpy.data.materials.get(item.name, None) + if not mat: + col.label(text="{} - is not available".format(item.name), icon="ERROR") + else: + split = col.split(percentage=0.75, align=True) + row = split.row(align=True) + row.label(text=item.name, translate=False, icon="MATERIAL_DATA") + subrow = split.row(align=True) + subrow.alignment = "RIGHT" + subrow.label(text="", icon="PINNED" if item.mat_fake_user else "BLANK1") + subrow = split.row(align=True) + subrow.alignment = "RIGHT" + subrow.label(text="", icon="LINK_BLEND" if item.mat_lib else "BLANK1") + + +def draw_ui_list_popups(self, context, obj_data=False): + layout = self.layout + + if not self.is_not_undo: + layout.label(text=UNDO_MESSAGE, icon="INFO") + return + + matlen = len(context.scene.mat_specials_mats) + matdata = "in Object's Data" if obj_data else "in Data" + matgramma = "Material" if matlen == 1 else "Materials" + matcount = "No" if matlen < 1 else matlen + + box = layout.box() + col = box.column(align=True) + col.label(text="{} {} {}".format(matcount, matgramma, matdata), + icon="INFO") + sub_split = col.split(percentage=0.7, align=True) + sub_box_1 = sub_split.box() + sub_box_1.label("Name") + sub_split_2 = sub_split.split(percentage=0.5, align=True) + sub_box_2 = sub_split_2.box() + sub_box_2.label("Fake") + sub_box_3 = sub_split_2.box() + sub_box_3.label("Lib") + + col.template_list( + "VIEW3D_UL_assign_material_popup_ui", + 'mat_specials', + context.scene, + 'mat_specials_mats', + context.scene.mat_specials, + 'index_mat', + rows=10 + ) + return + + # Menu classes class VIEW3D_MT_assign_material(Menu): @@ -1485,26 +1684,45 @@ class VIEW3D_MT_assign_material(Menu): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' - if c_data_has_materials(): - # no materials - for material_name in bpy.data.materials.keys(): - mats = layout.operator("view3d.assign_material", - text=material_name, - icon='MATERIAL_DATA') - mats.matname = material_name - mats.is_existing = True - use_separator(self, context) - if (not context.active_object): # info why the add material is innactive layout.label(text="*No active Object in the Scene*", icon="INFO") use_separator(self, context) + mat_prop_name = context.scene.mat_specials.set_material_name - add_new = layout.operator("view3d.assign_material", - text="Add New", icon='ZOOMIN') + add_new = layout.operator( + "view3d.assign_material", + text="Add New", icon='ZOOMIN' + ) add_new.matname = mat_prop_name add_new.is_existing = False + if use_mat_menu_type() != 'POPUP': + use_separator(self, context) + + if c_data_has_materials(): + mat_entry = layout.column() + + if use_mat_menu_type() == 'POPUP': + matp = mat_entry.operator( + "view3d.assign_material", + icon='MATERIAL_DATA' + ) + matp.is_existing = True + elif use_mat_menu_type() == 'COLUMNS': + get_col_size = switch_to_column() + mat_entry = layout.column_flow(columns=get_col_size) + + if use_mat_menu_type() in {'COLUMNS', 'STANDARD'}: + for material_name in bpy.data.materials.keys(): + mats = mat_entry.operator( + "view3d.assign_material", + text=material_name, + icon='MATERIAL_DATA' + ) + mats.matname = material_name + mats.is_existing = True + class VIEW3D_MT_select_material(Menu): bl_label = "Select by Material" @@ -1512,28 +1730,58 @@ class VIEW3D_MT_select_material(Menu): def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' - ob = context.object + obj = context.active_object + has_users = False + col = layout.column() + + if not c_data_has_materials(): + layout.label(text="*No Materials in the Data*", icon="INFO") + return + elif not obj: + layout.label(text="*No Active Object*", icon="INFO") + return + elif obj.mode == "EDIT" and not c_obj_data_has_materials(obj): + layout.label(text="*No Materials in the Object's Data*", icon="INFO") + return + elif use_mat_menu_type() == 'POPUP': + col.operator( + "view3d.select_material_by_name", + text="Select by Material", + icon='HAND', + ) + return - if (not c_data_has_materials()): - layout.label(text="*No Materials in the data*", icon="INFO") - elif (not ob): - layout.label(text="*No Objects to select*", icon="INFO") + if obj.mode == 'OBJECT': + if use_mat_menu_type() == 'COLUMNS': + get_col_size = switch_to_column(is_edit=False) + col = layout.column_flow(columns=get_col_size) + # show all used materials in entire blend file + for material_name, material in bpy.data.materials.items(): + if material.users > 0: + has_users = True + col.operator( + "view3d.select_material_by_name", + text=material_name, + icon='MATERIAL_DATA', + ).matname = material_name + if not has_users: + layout.label(text="*No users for Materials in the data*", icon="INFO") + return + elif obj.mode == 'EDIT': + if use_mat_menu_type() == 'COLUMNS': + get_col_size = switch_to_column(is_edit=True) + col = layout.column_flow(columns=get_col_size) + # show only the materials on this object + mats = obj.material_slots.keys() + for m in mats: + if m not in "": # skip empty slots + col.operator( + "view3d.select_material_by_name", + text=m, + icon='MATERIAL_DATA' + ).matname = m else: - if ob.mode == 'OBJECT': - # show all used materials in entire blend file - for material_name, material in bpy.data.materials.items(): - if (material.users > 0): - layout.operator("view3d.select_material_by_name", - text=material_name, - icon='MATERIAL_DATA', - ).matname = material_name - elif ob.mode == 'EDIT': - # show only the materials on this object - mats = ob.material_slots.keys() - for m in mats: - layout.operator("view3d.select_material_by_name", - text=m, - icon='MATERIAL_DATA').matname = m + layout.label(text="*Works only in Object and Edit mode*", icon="INFO") class VIEW3D_MT_remove_material(Menu): @@ -1543,24 +1791,35 @@ class VIEW3D_MT_remove_material(Menu): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' - layout.operator("view3d.clean_material_slots", - text="Clean Material Slots", - icon='COLOR_BLUE') - use_separator(self, context) + if context.mode in {'PAINT_TEXTURE'}: + layout.label( + text="Removing materials can lead to loss of painting data", + icon="INFO" + ) + use_separator(self, context) - if not c_render_engine("Lux"): - layout.operator("view3d.material_remove_slot", icon='COLOR_GREEN') - layout.operator("view3d.material_remove_object", icon='COLOR_RED') + layout.operator( + "view3d.clean_material_slots", + text="Clean Material Slots", + icon='COLOR_BLUE' + ) + use_separator(self, context) - if use_remove_mat_all(): - use_separator(self, context) - layout.operator("view3d.material_remove_all", - text="Remove Material Slots " - "(All Selected Objects)", - icon='CANCEL') - else: + if c_render_engine("Lux"): layout.label(text="Sorry, other Menu functions are", icon="INFO") layout.label(text="unvailable with Lux Renderer") + return + + layout.operator("view3d.material_remove_slot", icon='COLOR_GREEN') + layout.operator("view3d.material_remove_object", icon='COLOR_RED') + + if use_remove_mat_all(): + use_separator(self, context) + layout.operator( + "view3d.material_remove_all", + text="Remove Material Slots (All Selected Objects)", + icon='CANCEL' + ) class VIEW3D_MT_master_material(Menu): @@ -1574,8 +1833,13 @@ class VIEW3D_MT_master_material(Menu): layout.operator("view3d.show_mat_preview", icon="VISIBLE_IPO_ON") use_separator(self, context) - layout.menu("VIEW3D_MT_assign_material", icon='ZOOMIN') - layout.menu("VIEW3D_MT_select_material", icon='HAND') + if use_mat_menu_type() == 'POPUP': + VIEW3D_MT_assign_material.draw(self, context) + use_separator(self, context) + VIEW3D_MT_select_material.draw(self, context) + else: + layout.menu("VIEW3D_MT_assign_material", icon='ZOOMIN') + layout.menu("VIEW3D_MT_select_material", icon='HAND') use_separator(self, context) layout.operator("view3d.copy_material_to_selected", icon="COPY_ID") @@ -1665,8 +1929,15 @@ def menu_func(self, context): layout.operator_context = 'INVOKE_REGION_WIN' use_separator(self, context) - layout.menu("VIEW3D_MT_assign_material", icon='ZOOMIN') - layout.menu("VIEW3D_MT_select_material", icon='HAND') + if use_mat_menu_type() == 'POPUP': + VIEW3D_MT_assign_material.draw(self, context) + use_separator(self, context) + VIEW3D_MT_select_material.draw(self, context) + else: + layout.menu("VIEW3D_MT_assign_material", icon='ZOOMIN') + layout.menu("VIEW3D_MT_select_material", icon='HAND') + use_separator(self, context) + layout.operator("view3d.replace_material", text='Replace Material', icon='ARROW_LEFTRIGHT') @@ -1741,11 +2012,15 @@ class MATERIAL_PT_scenemassive(Panel): def poll(cls, context): return (enable_converters() is True and converter_type('BI_CONV')) + def draw_header(self, context): + layout = self.layout + help_panel_header(layout, menu_type="HELP_MT_biconvert") + def draw(self, context): layout = self.layout sc = context.scene - row = layout.row() - box = row.box() + col = layout.column(align=True) + box = col.box() split = box.box().split(0.5) split.operator("ml.refresh", @@ -1757,14 +2032,9 @@ class MATERIAL_PT_scenemassive(Panel): icon='MATERIAL') ml_restore.switcher = False ml_restore.renderer = "BI" - - box = layout.box() - row = box.row() row.menu("scenemassive.opt", text="Advanced Options", icon='SCRIPTWIN') - row.menu("HELP_MT_biconvert", - text="Usage Information Guide", icon="INFO") - box = layout.box() + box = col.box() box.label("Save Directory") split = box.split(0.85) split.prop(sc.mat_specials, "conv_path", text="", icon="RENDER_RESULT") @@ -1783,28 +2053,32 @@ class MATERIAL_PT_xps_convert(Panel): def poll(cls, context): return (enable_converters() is True and converter_type('CYC_CONV')) + def draw_header(self, context): + layout = self.layout + help_panel_header(layout, menu_type="help.nodeconvert") + def draw(self, context): layout = self.layout - row = layout.row() - box = row.box() + col = layout.column(align=True) + box = col.box() - box.label(text="Multi Image Support (Imports)") + box.label(text="Multi Image Support (Imports)", icon="INFO") split = box.box().split(0.5) - split.operator("xps_tools.convert_to_cycles_all", - text="Convert All to Nodes", icon="TEXTURE") - split.operator("xps_tools.convert_to_cycles_selected", - text="Convert Selected to Nodes", icon="TEXTURE") + split.operator( + "xps_tools.convert_to_cycles_all", + text="Convert All to Nodes", icon="TEXTURE" + ) + split.operator( + "xps_tools.convert_to_cycles_selected", + text="Convert Selected to Nodes", icon="TEXTURE" + ) - box = layout.box() - row = box.row() - ml_restore = row.operator("ml.restore", text="To BI Nodes ON", + box = col.box() + ml_restore = box.operator("ml.restore", text="To BI Nodes ON", icon='MATERIAL') ml_restore.switcher = True ml_restore.renderer = "BI" - row.menu("help.nodeconvert", - text="Usage Information Guide", icon="INFO") - # Converters Help # @@ -1826,12 +2100,12 @@ class MATERIAL_MT_biconv_help(Menu): layout.label(text="Select the texture loaded in the image node") layout.label(text="Press Ctrl/T to create the image nodes") layout.label(text="In the Node Editor, Select the Diffuse Node") - layout.label(text="Enable Node Wrangler addon", icon="NODETREE") + layout.label(text="Enable Node Wrangler add-on", icon="NODETREE") layout.label(text="If Unconnected or No Image Node Error:", icon="MOD_EXPLODE") use_separator(self, context) layout.label(text="Extract Alpha: the images have to have alpha channel") layout.label(text="The default path is the folder where the current .blend is") - layout.label(text="During Baking, the script will check writting privileges") + layout.label(text="During Baking, the script will check writing privileges") layout.label(text="Set the save path for extracting images with full access") layout.label(text="May Require Run As Administrator on Windows OS", icon="ERROR") layout.label(text="Converts Bi Textures to Image Files:", icon="MOD_EXPLODE") @@ -1861,7 +2135,7 @@ class MATERIAL_MT_nodeconv_help(Menu): layout.label(text="Select the texture loaded in the image node") layout.label(text="Press Ctrl/T to create the image nodes") layout.label(text="In the Node Editor, Select the Diffuse Node") - layout.label(text="Enable Node Wrangler addon", icon="NODETREE") + layout.label(text="Enable Node Wrangler add-on", icon="NODETREE") layout.label(text="If Unconnected or No Image Node Error:", icon="MOD_EXPLODE") use_separator(self, context) layout.label(text="For Specular Nodes, Image color influence has to be enabled") @@ -1869,7 +2143,7 @@ class MATERIAL_MT_nodeconv_help(Menu): layout.label(text="The Converter report can point out to some failures") layout.label(text="Not all Files will produce good results", icon="ERROR") layout.label(text="fbx, .dae, .obj, .3ds, .xna and more") - layout.label(text="**Supports Imported Files**:", icon="IMPORT") + layout.label(text="*Supports Imported Files*:", icon="IMPORT") use_separator(self, context) layout.label(text="For some file types") layout.label(text="Supports Alpha, Normals, Specular and Diffuse") @@ -1880,7 +2154,7 @@ class MATERIAL_MT_nodeconv_help(Menu): # Make Report -class material_converter_report(Operator): +class MATERIAL_OT_converter_report(Operator): bl_idname = "mat_converter.reports" bl_label = "Material Converter Report" bl_description = "Report about done Material Conversions" @@ -1901,208 +2175,309 @@ class material_converter_report(Operator): return context.window_manager.invoke_props_dialog(self, width=500) def execute(self, context): + return {'FINISHED'} # Scene Properties +class material_specials_scene_mats(PropertyGroup): + name = StringProperty() + mat_lib = BoolProperty( + default=False + ) + mat_fake_user = BoolProperty( + default=False + ) + + class material_specials_scene_props(PropertyGroup): conv_path = StringProperty( - name="Save Directory", - description=("Path to save images during conversion \n" - "Default is the location of the blend file"), - default="//", - subtype='DIR_PATH', - ) + name="Save Directory", + description="Path to save images during conversion\n" + "Default is the location of the blend file", + default="//", + subtype='DIR_PATH' + ) EXTRACT_ALPHA = BoolProperty( - attr="EXTRACT_ALPHA", - default=False, - description=("Extract Alpha channel from non-procedural images \n" - "Don't use this option if the image doesn't have Alpha"), - ) + attr="EXTRACT_ALPHA", + default=False, + description="Extract Alpha channel from non-procedural images\n" + "Don't use this option if the image doesn't have Alpha" + ) SET_FAKE_USER = BoolProperty( - attr="SET_FAKE_USER", - default=False, - description="Set fake user on unused images, so they can be kept in the .blend", - ) + attr="SET_FAKE_USER", + default=False, + description="Set fake user on unused images, so they can be kept in the .blend" + ) EXTRACT_PTEX = BoolProperty( - attr="EXTRACT_PTEX", - default=False, - description="Extract procedural images and bake them to jpeg", - ) + attr="EXTRACT_PTEX", + default=False, + description="Extract procedural images and bake them to jpeg" + ) EXTRACT_OW = BoolProperty( - attr="Overwrite", - default=False, - description="Extract textures again instead of re-using priorly extracted textures", - ) + attr="Overwrite", + default=False, + description="Extract textures again instead of re-using previously extracted textures" + ) SCULPT_PAINT = BoolProperty( - attr="SCULPT_PAINT", - default=False, - description=("Conversion geared towards sculpting and painting.\n" - "Creates a diffuse, glossy mixed with layer weight. \n" - "Image nodes are not connected"), - ) + attr="SCULPT_PAINT", + default=False, + description="Conversion geared towards sculpting and painting.\n" + "Creates a diffuse, glossy mixed with layer weight.\n" + "Image nodes are not connected" + ) UV_UNWRAP = BoolProperty( - attr="UV_UNWRAP", - default=False, - description=("Use automatical Angle based UV Unwrap of the active Object"), - ) + attr="UV_UNWRAP", + default=False, + description="Use automatic Angle based UV Unwrap for the Active Object" + ) enable_report = BoolProperty( - attr="enable_report", - default=False, - description=("Enable Converter Report in the UI"), - ) + attr="enable_report", + default=False, + description="Enable Converter Report in the UI" + ) img_bake_size = EnumProperty( - name="Bake Image Size", - description="Set the resolution size of baked images \n", - items=(('512', "Set : 512 x 512", "Bake Resolution 512 x 512"), - ('1024', "Set : 1024 x 1024", "Bake Resolution 1024 x 1024"), - ('2048', "Set : 2048 x 2048", "Bake Resolution 2048 x 2048")), - default='1024', - ) + name="Bake Image Size", + description="Set the resolution size of baked images \n", + items=( + ('512', "Set : 512 x 512", "Bake Resolution 512 x 512"), + ('1024', "Set : 1024 x 1024", "Bake Resolution 1024 x 1024"), + ('2048', "Set : 2048 x 2048", "Bake Resolution 2048 x 2048") + ), + default='1024' + ) set_material_name = StringProperty( - name="New Material name", - description="What Base name pattern to use for a new created Material\n" - "It is appended by an automatic numeric pattern depending\n" - "on the number of Scene's materials containing the Base", - default="Material_New", - maxlen=128, - ) + name="New Material name", + description="What Base name pattern to use for a new created Material\n" + "It is appended by an automatic numeric pattern depending\n" + "on the number of Scene's materials containing the Base", + default="Material_New", + maxlen=128 + ) use_tweak = BoolProperty( name="Tweak Settings", description="Open Preview Active Material after new Material creation", - default=False, - ) + default=False + ) + index_mat = IntProperty( + name="index", + options={"HIDDEN"} + ) -# Addon Preferences +# Add-on Preferences class VIEW3D_MT_material_utils_pref(AddonPreferences): bl_idname = __name__ show_warnings = BoolProperty( - name="Enable Warning messages", - default=False, - description="Show warning messages \n" - "when an action is executed or failed.\n \n" - "Advisable if you don't know how the tool works", - ) + name="Enable Warning messages", + default=False, + description="Show warning messages when an action is executed or failed" + ) show_remove_mat = BoolProperty( - name="Enable Remove all Materials", - default=False, - description="Enable Remove all Materials for all Selected Objects\n\n" - "Use with care - if you want to keep materials after\n" - "closing or reloading Blender, Set Fake User for them", - ) + name="Enable Remove all Materials", + default=False, + description="Enable Remove all Materials for all Selected Objects\n\n" + "Use with care - if you want to keep materials after\n" + "closing or reloading Blender, Set Fake User for them" + ) show_mat_preview = BoolProperty( - name="Enable Material Preview", - default=True, - description="Material Preview of the Active Object \n" - "Contains the preview of the active Material, \n" - "Use nodes, Color, Specular and Transparency \n" - "settings depending on the Context and Preferences", - ) + name="Enable Material Preview", + default=True, + description="Material Preview of the Active Object\n" + "Contains the preview of the active Material,\n" + "Use nodes, Color, Specular and Transparency\n" + "settings depending on the Context and Preferences" + ) set_cleanmatslots = BoolProperty( - name="Enable Auto Clean", - default=True, - description="Enable Automatic Removal of unused Material Slots \n" - "called together with the Assign Material menu option. \n \n" - "Apart from preference and the cases when it affects \n" - "adding materials, enabling it can have some \n" - "performance impact on very dense meshes", - ) + name="Enable Auto Clean", + default=True, + description="Enable Automatic Removal of unused Material Slots\n" + "called together with the Assign Material menu option.\n" + "Apart from preference and the cases when it affects\n" + "adding materials, enabling it can have some\n" + "performance impact on very dense meshes" + ) show_separators = BoolProperty( - name="Use Separators in the menus", - default=True, - description="Use separators in the menus, a trade-off between \n" - "readability vs. using more space for displaying items", - ) + name="Use Separators in the menus", + default=True, + description="Use separators in the menus, a trade-off between\n" + "readability vs. using more space for displaying items" + ) show_converters = BoolProperty( - name="Enable Converters", - default=False, - description="Enable Material Converters", - ) + name="Enable Converters", + default=False, + description="Enable Material Converters" + ) set_preview_size = EnumProperty( - name="Preview Menu Size", - description="Set the preview menu size \n" - "depending on the number of materials \n" - "in the scene (width and height)", - items=(('2x2', "Size 2x2", "Width 2 Height 2"), - ('2x3', "Size 2x3", "Width 3 Height 2"), - ('3x3', "Size 3x3", "Width 3 Height 3"), - ('3x4', "Size 3x4", "Width 4 Height 3"), - ('4x4', "Size 4x4", "Width 4 Height 4"), - ('5x5', "Size 5x5", "Width 5 Height 5"), - ('6x6', "Size 6x6", "Width 6 Height 6"), - ('0x0', "List", "Display as a List")), - default='3x3', - ) + name="Preview Menu Size", + description="Set the preview menu size\n" + "depending on the number of materials\n" + "in the scene (width and height)", + items=( + ('2x2', "Size 2x2", "Width 2 Height 2"), + ('2x3', "Size 2x3", "Width 3 Height 2"), + ('3x3', "Size 3x3", "Width 3 Height 3"), + ('3x4', "Size 3x4", "Width 4 Height 3"), + ('4x4', "Size 4x4", "Width 4 Height 4"), + ('5x5', "Size 5x5", "Width 5 Height 5"), + ('6x6', "Size 6x6", "Width 6 Height 6"), + ('0x0', "List", "Display as a List") + ), + default='3x3' + ) set_preview_type = EnumProperty( - name="Preview Menu Type", - description="Set the the Preview menu type", - items=(('LIST', "Classic", - "Display as a Classic List like in Blender Propreties.\n" - "Preview of Active Material is not available"), - ('PREVIEW', "Preview Display", - "Display as a preview of Thumbnails\n" - "It can have some performance issues with scenes containing a lot of materials\n" - "Preview of Active Material is available")), - default='PREVIEW', - ) + name="Preview Menu Type", + description="Set the the Preview menu type", + items=( + ('LIST', "Classic", + "Display as a Classic List like in Blender Properties.\n" + "Preview of Active Material is not available\n\n" + "Note: Choosing a different material from the list will replace the active one"), + ('PREVIEW', "Preview Display", + "Display as a preview of Thumbnails\n" + "It can have some performance issues with scenes containing a lot of materials\n" + "Preview of Active Material is available\n\n" + "Note: Choosing a different material from the list will replace the active one") + ), + default='PREVIEW' + ) set_experimental_type = EnumProperty( - name="Experimental Features", - description="Set the Type of converters enabled", - items=(('ALL', "All Converters", - "Enable all Converters"), - ('CYC_CONV', "BI and Cycles Nodes", - "Enable Cycles related Convert"), - ('BI_CONV', "BI To Cycles", - "Enable Blender Internal related Converters")), - default='ALL', - ) + name="Experimental Features", + description="Set the type of converters enabled", + items=( + ('ALL', "All Converters", + "Enable all Converters"), + ('CYC_CONV', "BI and Cycles Nodes", + "Enable Cycles related Convert"), + ('BI_CONV', "BI To Cycles", + "Enable Blender Internal related Converters") + ), + default='ALL', + ) + set_add_material_menu = EnumProperty( + name="Add Material Menu", + description="Set the type of Add Material menu", + items=( + ('STANDARD', "Standard Menu", + "Material entries in the menu are bellow each other"), + ('COLUMNS', "Column Menu", + "Material entries are placed in column blocks"), + ('POPUP', "Pop up Menu", + "Material entries are placed in a scrollable list inside a pop-up menu") + ), + default='POPUP' + ) def draw(self, context): layout = self.layout sc = context.scene - box = layout.box() + col_m = layout.column(align=True) + + box = col_m.box() box.label("Save Directory") split = box.split(0.85) split.prop(sc.mat_specials, "conv_path", text="", icon="RENDER_RESULT") - split.operator("material.check_converter_path", - text="", icon="EXTERNAL_DATA") - - box = layout.box() + split.operator( + "material.check_converter_path", + text="", icon="EXTERNAL_DATA" + ) + box = col_m.box() split = box.split(align=True) - col = split.column() + col = split.column(align=True) col.prop(self, "show_warnings") col.prop(self, "show_remove_mat") - - col = split.column() - col.alignment = 'RIGHT' col.prop(self, "set_cleanmatslots") col.prop(self, "show_separators") - boxie = box.box() - split = boxie.split(percentage=0.3) + col = split.column(align=True) + col.label("Apply / Select Material mode:") + col.prop(self, "set_add_material_menu", expand=True) + + box = col_m.box() + size_split = 0.3 if self.show_mat_preview else 1.0 + split = box.split(percentage=size_split, align=True) split.prop(self, "show_mat_preview") + if self.show_mat_preview: - rowsy = split.row(align=True) - rowsy.enabled = True if self.show_mat_preview else False - rowsy.prop(self, "set_preview_type", expand=True) - rowsa = boxie.row(align=True) - rowsa.enabled = True if self.set_preview_type in {'PREVIEW'} else False - rowsa.prop(self, "set_preview_size", expand=True) - - boxif = box.box() - split = boxif.split(percentage=0.3) + subsplit = split.split(percentage=0.7, align=True) + row = subsplit.row(align=True) + row.prop(self, "set_preview_type", expand=True) + + subrow = subsplit.row(align=True) + subrow.enabled = True if self.set_preview_type in {'PREVIEW'} else False + subrow.prop(self, "set_preview_size", text="") + + box = col_m.box() + size_split = 0.3 if self.show_converters else 1.0 + split = box.split(percentage=size_split, align=True) split.prop(self, "show_converters") + if self.show_converters: - rowe = split.row(align=True) - rowe.prop(self, "set_experimental_type", expand=True) + row = split.row(align=True) + row.prop(self, "set_experimental_type", expand=True) # utility functions: +def help_panel_header(layout, menu_type="VIEW3D_MT_master_material"): + layout.separator() + box = layout.box() + box.scale_y = 0.5 + box.menu(menu_type, text="", icon="INFO") + layout.separator() + + +def activate_mat_slot(actob, matname): + mats = actob.material_slots + for i, m in enumerate(mats): + if m.name == matname: + # make slot active + actob.active_material_index = i + break + + +def materials_lists_fill_names(context, refresh=False, is_object=False): + mats_list = context.scene.mat_specials_mats + if refresh: + for key in mats_list.keys(): + index = mats_list.find(key) + if index != -1: + mats_list.remove(index) + + obj = context.active_object + mat_collection = [] + if (is_object and obj): + mat_collection = [ + slot.material for slot in obj.material_slots if + slot.material + ] + else: + mat_collection = bpy.data.materials + + for mat in mat_collection: + if mat.name not in mats_list.keys() or refresh: + prop = mats_list.add() + prop.name = mat.name + prop.mat_lib = bool(mat.library) + prop.mat_fake_user = mat.use_fake_user + + +def switch_to_column(is_edit=False): + obj = bpy.context.active_object + collect = obj.material_slots if is_edit else bpy.data.materials + col_size = int(round(len(collect) / COLUMN_SPLIT)) + + return col_size if col_size > 0 else 1 + + +def remove_material_slot(): + if bpy.ops.object.material_slot_remove.poll(): + bpy.ops.object.material_slot_remove() + + def check_mat_name_unique(name_id="Material_new"): # check if the new name pattern is in materials' data name_list = [] @@ -2229,6 +2604,13 @@ def use_remove_mat_all(): return bool(show_rmv_mat) +def use_mat_menu_type(): + pref = return_preferences() + use_menu_mat = pref.set_add_material_menu + + return use_menu_mat + + def use_mat_preview(): pref = return_preferences() show_mat_prw = pref.show_mat_preview @@ -2283,8 +2665,12 @@ def register(): # Register Scene Properties bpy.types.Scene.mat_specials = PointerProperty( - type=material_specials_scene_props - ) + type=material_specials_scene_props + ) + bpy.types.Scene.mat_specials_mats = CollectionProperty( + name="Material name", + type=material_specials_scene_mats + ) kc = bpy.context.window_manager.keyconfigs.addon if kc: @@ -2310,6 +2696,7 @@ def unregister(): bpy.types.MATERIAL_MT_specials.remove(menu_func) del bpy.types.Scene.mat_specials + del bpy.types.Scene.mat_specials_mats bpy.utils.unregister_module(__name__) diff --git a/materials_utils/material_converter.py b/materials_utils/material_converter.py index dfde5c50..bf52bec1 100644 --- a/materials_utils/material_converter.py +++ b/materials_utils/material_converter.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- import bpy +import math from mathutils import Vector from bpy.types import Operator from .warning_messages_utils import ( - warning_messages, - c_is_cycles_addon_enabled, - c_data_has_materials, - collect_report, - ) + warning_messages, + c_is_cycles_addon_enabled, + c_data_has_materials, + collect_report, +) # ----------------------------------------------------------------------------- # Globals @@ -665,7 +666,7 @@ def makeCyclesFromBI(cmat): # Make Diffuse and Output nodes mainShader = makeMainShader(TreeNodes) - mainShader.inputs['Roughness'].default_value = cmat.specular_intensity + mainShader.inputs['Roughness'].default_value = math.sqrt(max(cmat.specular_intensity, 0.0)) mainDiffuse = mainShader materialOutput = makeMaterialOutput(TreeNodes) links.new(mainShader.outputs['BSDF'], materialOutput.inputs['Surface']) @@ -752,6 +753,7 @@ class material_convert_all(Operator): def execute(self, context): AutoNode(False, self) + return {'FINISHED'} @@ -766,13 +768,12 @@ class material_convert_selected(Operator): def poll(cls, context): return (bpy.data.filepath != "" and c_data_has_materials() and c_is_cycles_addon_enabled() and - bool(next((obj for obj in context.selected_objects if obj.type == 'MESH'), - None) - ) + bool(next((obj for obj in context.selected_objects if obj.type == 'MESH'), None)) ) def execute(self, context): AutoNode(True, self) + return {'FINISHED'} diff --git a/materials_utils/materials_cycles_converter.py b/materials_utils/materials_cycles_converter.py index c9dd994d..13e1e1ec 100644 --- a/materials_utils/materials_cycles_converter.py +++ b/materials_utils/materials_cycles_converter.py @@ -6,18 +6,18 @@ import bpy from os import path as os_path from bpy.types import Operator from math import ( - log2, ceil, - ) + log2, ceil, sqrt, +) from bpy.props import ( - BoolProperty, - EnumProperty, - ) + BoolProperty, + EnumProperty, +) from .warning_messages_utils import ( - warning_messages, - c_is_cycles_addon_enabled, - c_data_has_materials, - collect_report, - ) + warning_messages, + c_is_cycles_addon_enabled, + c_data_has_materials, + collect_report, +) # ----------------------------------------------------------------------------- # Globals @@ -130,7 +130,7 @@ def BakingText(tex, mode, tex_type=None): img = bpy.data.images.get("TMP_BAKING") img.file_format = ("JPEG" if not mode == "ALPHA" else "PNG") - # switch temporarly to 'IMAGE EDITOR', other approaches are not reliable + # switch temporarily to 'IMAGE EDITOR', other approaches are not reliable check_area = False store_area = bpy.context.area.type collect_report("INFO: Temporarly switching context to Image Editor") @@ -267,8 +267,7 @@ def AutoNode(active=False, operator=None): for n in TreeNodes.nodes: TreeNodes.nodes.remove(n) - # Starting point is diffuse BSDF and output material - # and a Color Ramp node + # Starting point is diffuse BSDF and output material and a Color Ramp node shader = TreeNodes.nodes.new('ShaderNodeBsdfDiffuse') shader.location = 10, 10 shader_val = TreeNodes.nodes.new('ShaderNodeValToRGB') @@ -382,7 +381,7 @@ def AutoNode(active=False, operator=None): else: # Create Clay Material (Diffuse, Glossy, Layer Weight) shader.inputs['Color'].default_value = PAINT_SC_COLOR - shader.inputs['Roughness'].default_value = 0.9 + shader.inputs['Roughness'].default_value = 0.9486 # remove Color Ramp and links from the default shader and reroute try: @@ -416,10 +415,10 @@ def AutoNode(active=False, operator=None): shader.inputs['Roughness'].default_value = cmat.specular_intensity if shader.type == 'ShaderNodeBsdfGlossy': - shader.inputs['Roughness'].default_value = 1 - cmat.raytrace_mirror.gloss_factor + shader.inputs['Roughness'].default_value = sqrt(max(1 - cmat.raytrace_mirror.gloss_factor, 0.0)) if shader.type == 'ShaderNodeBsdfGlass': - shader.inputs['Roughness'].default_value = 1 - cmat.raytrace_mirror.gloss_factor + shader.inputs['Roughness'].default_value = sqrt(max(1 - cmat.raytrace_mirror.gloss_factor, 0.0)) shader.inputs['IOR'].default_value = cmat.raytrace_transparency.ior if shader.type == 'ShaderNodeEmission': @@ -848,6 +847,21 @@ def create_mix_node(TreeNodes, links, nodes, loc, start, median_point, row, fram return mix_node +def unwrap_active_object(context): + enable_unwrap = context.scene.mat_specials.UV_UNWRAP + if enable_unwrap: + obj_name = getattr(context.active_object, "name", "UNNAMED OBJECT") + try: + # it's possible that the active object would fail UV Unwrap + bpy.ops.object.editmode_toggle() + bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001) + bpy.ops.object.editmode_toggle() + collect_report("INFO: UV Unwrapping active object {}".format(obj_name)) + except: + collect_report("ERROR: UV Unwrapping failed for " + "active object {}".format(obj_name)) + + # ----------------------------------------------------------------------------- # Operator Classes @@ -866,10 +880,8 @@ class mllock(Operator): TreeNodes = cmat.node_tree for n in TreeNodes.nodes: if n.type == 'ShaderNodeOutputMaterial': - if n.label == 'Locked': - n.label = '' - else: - n.label = 'Locked' + n.label = "" if n.label == "Locked" else "Locked" + return {'FINISHED'} @@ -882,28 +894,17 @@ class mlrefresh(Operator): @classmethod def poll(cls, context): - return (bpy.data.filepath != ""and c_is_cycles_addon_enabled() and + return (bpy.data.filepath != "" and c_is_cycles_addon_enabled() and c_data_has_materials()) def execute(self, context): AutoNodeInitiate(False, self) if CHECK_AUTONODE is True: - enable_unwrap = bpy.context.scene.mat_specials.UV_UNWRAP - if enable_unwrap: - obj_name = getattr(context.active_object, "name", "UNNAMED OBJECT") - try: - # it's possible to the active object would fail UV Unwrap - bpy.ops.object.editmode_toggle() - bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001) - bpy.ops.object.editmode_toggle() - collect_report("INFO: UV Unwrapping active object " - "{}".format(obj_name)) - except: - collect_report("ERROR: UV Unwrapping failed for " - "active object {}".format(obj_name)) + unwrap_active_object(context) collect_report("Conversion finished !", False, True) + return {'FINISHED'} @@ -922,20 +923,10 @@ class mlrefresh_active(Operator): def execute(self, context): AutoNodeInitiate(True, self) if CHECK_AUTONODE is True: - obj_name = getattr(context.active_object, "name", "UNNAMED OBJECT") - enable_unwrap = bpy.context.scene.mat_specials.UV_UNWRAP - if enable_unwrap: - try: - # you can already guess it, what could happen here - bpy.ops.object.editmode_toggle() - bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001) - bpy.ops.object.editmode_toggle() - collect_report("INFO: UV Unwrapping object {}".format(obj_name)) - except: - collect_report("ERROR: UV Unwrapping failed for " - "object {}".format(obj_name)) + unwrap_active_object(context) collect_report("Conversion finished !", False, True) + return {'FINISHED'} @@ -947,27 +938,28 @@ class mlrestore(Operator): bl_options = {'REGISTER', 'UNDO'} switcher = BoolProperty( - name="Use Nodes", - description="When restoring, switch Use Nodes On/Off", - default=True - ) + name="Use Nodes", + description="When restoring, switch Use Nodes On/Off", + default=True + ) renderer = EnumProperty( - name="Renderer", - description="Choose Cycles or Blender Internal", - items=(('CYCLES', "Cycles", "Switch to Cycles"), - ('BI', "Blender Internal", "Switch to Blender Internal")), - default='CYCLES', - ) + name="Renderer", + description="Choose Cycles or Blender Internal", + items=( + ('CYCLES', "Cycles", "Switch to Cycles"), + ('BI', "Blender Internal", "Switch to Blender Internal") + ), + default='CYCLES', + ) @classmethod def poll(cls, context): return c_is_cycles_addon_enabled() def execute(self, context): - if self.switcher: - AutoNodeSwitch(self.renderer, "ON", self) - else: - AutoNodeSwitch(self.renderer, "OFF", self) + switch = "ON" if self.switcher else "OFF" + AutoNodeSwitch(self.renderer, switch, self) + return {'FINISHED'} diff --git a/materials_utils/texture_rename.py b/materials_utils/texture_rename.py index c803295e..585a3a7d 100644 --- a/materials_utils/texture_rename.py +++ b/materials_utils/texture_rename.py @@ -3,31 +3,41 @@ import bpy from bpy.types import ( - Operator, - Panel, - ) -from bpy.props import StringProperty + Operator, + Panel, +) +from bpy.props import ( + BoolProperty, + StringProperty, +) from .warning_messages_utils import ( - warning_messages, - c_data_has_images, - ) + warning_messages, + c_data_has_images, +) class TEXTURE_OT_patern_rename(Operator): bl_idname = "texture.patern_rename" bl_label = "Texture Renamer" bl_description = ("Replace the Texture names pattern with the attached Image ones\n" - "Works on all Textures (Including Brushes) \n \n" - "The First field - the name pattern to replace \n" - "The Second - searches for existing names \n") + "Works on all Textures (Including Brushes)\n" + "The First field - the name pattern to replace\n" + "The Second - search for existing names") bl_options = {'REGISTER', 'UNDO'} def_name = "Texture" # default name is_not_undo = False # prevent drawing props on undo + named = StringProperty( - name="Search for name", - default=def_name - ) + name="Search for name", + description="Enter the name pattern or choose the one from the dropdown list below", + default=def_name + ) + replace_all = BoolProperty( + name="Replace all", + description="Replace all the Textures in the data with the names of the images attached", + default=False + ) @classmethod def poll(cls, context): @@ -35,28 +45,35 @@ class TEXTURE_OT_patern_rename(Operator): def draw(self, context): layout = self.layout - if self.is_not_undo is True: - box = layout.box() - box.prop(self, "named", text="Name pattern", icon="SYNTAX_ON") - layout.separator() + if not self.is_not_undo: + layout.label(text="*Only Undo is available*", icon="INFO") + return + + layout.prop(self, "replace_all") - box = layout.box() - box.prop_search(self, "named", bpy.data, "textures") - else: - layout.label(text="**Only Undo is available**", icon="INFO") + box = layout.box() + box.enabled = not self.replace_all + box.prop(self, "named", text="Name pattern", icon="SYNTAX_ON") + + box = layout.box() + box.enabled = not self.replace_all + box.prop_search(self, "named", bpy.data, "textures") def invoke(self, context, event): self.is_not_undo = True return context.window_manager.invoke_props_dialog(self) + def check(self, context): + return self.is_not_undo + def execute(self, context): errors = [] # collect texture names without images attached - tex_count = 0 # check if there is textures at all + tex_count = len(bpy.data.textures) for texture in bpy.data.textures: try: - if texture and self.named in texture.name and texture.type in {"IMAGE"}: - tex_count += 1 + is_allowed = self.named in texture.name if not self.replace_all else True + if texture and is_allowed and texture.type in {"IMAGE"}: textname = "" img = (bpy.data.textures[texture.name].image if bpy.data.textures[texture.name] else None) if not img: @@ -67,7 +84,7 @@ class TEXTURE_OT_patern_rename(Operator): else: break texture.name = textname - if texture.type != "IMAGE": # rename specific textures as clouds, environnement map,... + if texture.type != "IMAGE": # rename specific textures as clouds, environment map... texture.name = texture.type.lower() except: continue @@ -86,7 +103,6 @@ class TEXTURE_OT_patern_rename(Operator): class TEXTURE_PT_rename_panel(Panel): - # Creates a Panel in the scene context of the properties editor bl_label = "Texture Rename" bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' @@ -98,13 +114,13 @@ class TEXTURE_PT_rename_panel(Panel): def register(): - bpy.utils.register_module(__name__) - pass + bpy.utils.register_class(TEXTURE_OT_patern_rename) + bpy.utils.register_class(TEXTURE_PT_rename_panel) def unregister(): - bpy.utils.unregister_module(__name__) - pass + bpy.utils.unregister_class(TEXTURE_PT_rename_panel) + bpy.utils.unregister_class(TEXTURE_OT_patern_rename) if __name__ == "__main__": diff --git a/materials_utils/warning_messages_utils.py b/materials_utils/warning_messages_utils.py index 2e5f3b69..00f4a719 100644 --- a/materials_utils/warning_messages_utils.py +++ b/materials_utils/warning_messages_utils.py @@ -21,7 +21,7 @@ def warning_messages(operator=None, warn='DEFAULT', object_name="", is_mat=None, # a list of strings can be passed and concatenated in obj_name too # is_mat a switch to change to materials or textures for obj_name('MAT','TEX', 'FILE', None) # fake - optional string that can be passed - # MAX_COUNT - max members of an list to be displayed + # MAX_COUNT - max members of an list to be displayed in UI report # override - important messages that should be enabled, no matter the setting # pass the show_warnings bool to enable/disable them @@ -72,6 +72,8 @@ def warning_messages(operator=None, warn='DEFAULT', object_name="", is_mat=None, "not cleaned"), 'C_OB_MIX_NO_MAT': "{}{}".format(obj_name, "No Materials or an Object type that " "can't have Materials (Clean Material Slots)"), + 'C_OB_MIX_SLOT_MAT': "{}{}".format(obj_name, "No Materials or only empty Slots are removed " + "(Clean Material Slots)"), 'R_OB_NO_MAT': "{}{}".format(obj_name, "No Materials. Nothing to remove"), 'R_OB_FAIL_MAT': "{}{}".format(obj_name, "Failed to remove materials - (Operator Error)"), 'R_NO_SL_MAT': "No Selection. Material slots are not removed", @@ -79,6 +81,7 @@ def warning_messages(operator=None, warn='DEFAULT', object_name="", is_mat=None, 'R_ALL_NO_MAT': "Object(s) have no materials to remove", 'R_ACT_MAT': "{}{}".format(obj_name, "Removed active Material"), 'R_ACT_MAT_ALL': "{}{}".format(obj_name, "Removed all Material from the Object"), + 'SL_MAT_EDIT_BY_NAME': "{}{}{}".format("Geometry with the Material ", obj_name, "been selected"), 'SL_MAT_BY_NAME': "{}{}{}".format("Objects with the Material ", obj_name, "been selected"), 'OB_CANT_MAT': "{}{}".format(obj_name, "Object type that can't have Materials"), 'REP_MAT_NONE': "Replace Material: No materials replaced", @@ -123,17 +126,18 @@ def warning_messages(operator=None, warn='DEFAULT', object_name="", is_mat=None, operator.report({'INFO'}, message[warn]) if obj_size_big is True: - print("\n** MATERIAL SPECIALS **: \n Full list for the Info message is: \n", - ", ".join(object_name), "\n") + print("\n[Materials Utils Specials]:\nFull list for the Info message is:\n\n", + " ".join(names + "," + "\n" * ((i + 1) % 10 == 0) for i, names in enumerate(object_name)), + "\n") - # restore settings if overriden + # restore settings if overridden if override: addon.preferences.show_warnings = get_warn def collect_report(collection="", is_start=False, is_final=False): # collection passes a string for appending to COLLECT_REPORT global - # is_final swithes to the final report with the operator in __init__ + # is_final switches to the final report with the operator in __init__ global COLLECT_REPORT scene = bpy.context.scene.mat_specials use_report = scene.enable_report @@ -164,20 +168,14 @@ def c_data_has_materials(): return (len(bpy.data.materials) > 0) +def c_obj_data_has_materials(obj): + # check for material presence in object's data + matlen = 0 + if obj: + matlen = len(obj.data.materials) + return (matlen > 0) + + def c_data_has_images(): # check for image presence in data return (len(bpy.data.images) > 0) - - -def register(): - bpy.utils.register_module(__name__) - pass - - -def unregister(): - bpy.utils.unregister_module(__name__) - pass - - -if __name__ == "__main__": - register() diff --git a/measureit/__init__.py b/measureit/__init__.py index 489e0a9e..b5791bbf 100644 --- a/measureit/__init__.py +++ b/measureit/__init__.py @@ -29,7 +29,7 @@ bl_info = { "name": "MeasureIt", "author": "Antonio Vazquez (antonioya)", "location": "View3D > Tools Panel /Properties panel", - "version": (1, 7, 0), + "version": (1, 7, 1), "blender": (2, 7, 4), "description": "Tools for measuring objects.", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/" @@ -37,8 +37,6 @@ bl_info = { "category": "3D View" } -import sys -import os # ---------------------------------------------- # Import modules diff --git a/measureit/measureit_geometry.py b/measureit/measureit_geometry.py index 8b0b8371..1617ed5a 100644 --- a/measureit/measureit_geometry.py +++ b/measureit/measureit_geometry.py @@ -75,7 +75,7 @@ def draw_segments(context, myobj, op, region, rv3d): # -------------------- # Loop # -------------------- - for idx in range(0, op.measureit_num): + for idx in range(op.measureit_num): ms = op.measureit_segments[idx] if ovr is False: fsize = ms.glfont_size @@ -557,7 +557,7 @@ def draw_segments(context, myobj, op, region, rv3d): p_02a = None p_02b = None # draw the arc - for i in range(0, int(n_step)): + for i in range(int(n_step)): p2 = mat_trans2 * mat_rot1 * mat_trans1 * p1 p1_ = (p1[0] + vi[0], p1[1] + vi[1], p1[2] + vi[2]) # First Point @@ -606,7 +606,7 @@ def draw_segments(context, myobj, op, region, rv3d): for face in ms.measureit_faces: myvertices = [] for v in face.measureit_index: - myvertices.extend([v.glidx]) + myvertices.append(v.glidx) area = get_area_and_paint(myvertices, myobj, obverts, region, rv3d) tot += area @@ -668,7 +668,7 @@ def get_area_and_paint(myvertices, myobj, obverts, region, rv3d): bm = from_edit_mesh(myobj.data) myv = [] for v in bm.verts: - myv.extend([v.co]) + myv.append(v.co) tris = mesh_utils.ngon_tessellate(myv, myvertices) for t in tris: @@ -750,7 +750,7 @@ def get_group_sum(myobj, tag): scale = bpy.context.scene.unit_settings.scale_length tot = 0.0 obverts = get_mesh_vertices(myobj) - for idx in range(0, mp.measureit_num): + for idx in range(mp.measureit_num): ms = mp.measureit_segments[idx] if (ms.gltype == 1 or ms.gltype == 12 or ms.gltype == 13 or ms.gltype == 14) and ms.gltot != '99' \ diff --git a/measureit/measureit_main.py b/measureit/measureit_main.py index 89a48799..c85de95d 100644 --- a/measureit/measureit_main.py +++ b/measureit/measureit_main.py @@ -408,7 +408,7 @@ class MeasureitEditPanel(Panel): row = box.row(True) row.operator("measureit.expandallsegmentbutton", text="Expand all", icon="ZOOMIN") row.operator("measureit.collapseallsegmentbutton", text="Collapse all", icon="ZOOMOUT") - for idx in range(0, mp.measureit_num): + for idx in range(mp.measureit_num): if mp.measureit_segments[idx].glfree is False: add_item(box, idx, mp.measureit_segments[idx]) @@ -426,7 +426,7 @@ class MeasureitEditPanel(Panel): myobj = context.object obverts = get_mesh_vertices(myobj) viewtot = False - for idx in range(0, mp.measureit_num): + for idx in range(mp.measureit_num): ms = mp.measureit_segments[idx] if (ms.gltype == 1 or ms.gltype == 12 or ms.gltype == 13 or ms.gltype == 14) and ms.gltot != '99' \ @@ -472,7 +472,7 @@ class MeasureitEditPanel(Panel): box = layout.box() box.label("Totals", icon='SOLO_ON') final = 0 - for idx in range(0, len(tot)): + for idx in range(len(tot)): if ac[idx] is True: final += tot[idx] tx_dist = format_distance(fmt, units, tot[idx]) @@ -1030,7 +1030,7 @@ class AddSegmentOrtoButton(Operator): mainobject.MeasureGenerator.add() mp = mainobject.MeasureGenerator[0] - for x in range(0, len(mylist)): + for x in range(len(mylist)): # ----------------------- # Only if not exist # ----------------------- @@ -1658,7 +1658,7 @@ class DeleteAllSumButton(Operator): if context.object is not None: if 'MeasureGenerator' in context.object: mp = context.object.MeasureGenerator[0] - for idx in range(0, mp.measureit_num): + for idx in range(mp.measureit_num): ms = mp.measureit_segments[idx] ms.gltot = '99' @@ -2011,19 +2011,22 @@ def draw_main(context): rv3d = context.space_data.region_quadviews[i] scene = bpy.context.scene - # Get visible layers + local_view = context.area.spaces.active.local_view is not None layers = [] - if bpy.context.space_data.lock_camera_and_layers is True: - for x in range(0, 20): - if bpy.context.scene.layers[x] is True: - layers.extend([x]) - else: - for x in range(20): - if bpy.context.space_data.layers[x] is True: - layers.extend([x]) + if local_view is False: + # Get visible layers + if bpy.context.space_data.lock_camera_and_layers is True: + for x in range(20): + if bpy.context.scene.layers[x] is True: + layers.append(x) + else: + # Lock disabled, use view dependent visible layers + for x in range(20): + if bpy.context.space_data.layers[x] is True: + layers.append(x) # Display selected or all - if scene.measureit_gl_ghost is False: + if scene.measureit_gl_ghost is False or local_view is True: objlist = context.selected_objects else: objlist = context.scene.objects @@ -2036,13 +2039,18 @@ def draw_main(context): for myobj in objlist: if myobj.hide is False: if 'MeasureGenerator' in myobj: - # verify visible layer - for x in range(0, 20): - if myobj.layers[x] is True: - if x in layers: + if local_view is False: + # verify visible layer + for x in range(20): + if myobj.layers[x] is True and x in layers: op = myobj.MeasureGenerator[0] draw_segments(context, myobj, op, region, rv3d) - break + break + else: + # Layer check not needed here, selected objects are not + # added to context.selected_objects if in disabled layers + op = myobj.MeasureGenerator[0] + draw_segments(context, myobj, op, region, rv3d) # --------------------------------------- # Generate all OpenGL calls for debug # --------------------------------------- @@ -2120,7 +2128,7 @@ def get_selected_vertex(myobject): tv = len(bm.verts) for v in bm.verts: if v.select: - mylist.extend([v.index]) + mylist.append(v.index) if flag is True: bpy.ops.object.editmode_toggle() @@ -2154,7 +2162,7 @@ def get_selected_vertex_history(myobject): bm = from_edit_mesh(myobject.data) for v in bm.select_history: - mylist.extend([v.index]) + mylist.append(v.index) if flag is True: bpy.ops.object.editmode_toggle() @@ -2185,8 +2193,8 @@ def get_smart_selected(myobject): bm = from_edit_mesh(myobject.data) for e in bm.edges: if e.select is True: - mylist.extend([e.verts[0].index]) - mylist.extend([e.verts[1].index]) + mylist.append(e.verts[0].index) + mylist.append(e.verts[1].index) if flag is True: bpy.ops.object.editmode_toggle() @@ -2216,12 +2224,12 @@ def get_selected_faces(myobject): bm = from_edit_mesh(myobject.data) for e in bm.faces: - myface = [] + myfaces = [] if e.select is True: - for i in range(0, len(e.verts)): - myface.extend([e.verts[i].index]) + for i in range(len(e.verts)): + myfaces.append(e.verts[i].index) - mylist.extend([myface]) + mylist.extend([myfaces]) if flag is True: bpy.ops.object.editmode_toggle() diff --git a/measureit/measureit_render.py b/measureit/measureit_render.py index eb04c92d..d5eb6430 100644 --- a/measureit/measureit_render.py +++ b/measureit/measureit_render.py @@ -56,9 +56,9 @@ def render_main(self, context, animation=False): # Get visible layers layers = [] scene = context.scene - for x in range(0, 20): + for x in range(20): if scene.layers[x] is True: - layers.extend([x]) + layers.append(x) # Get object list objlist = context.scene.objects @@ -124,8 +124,8 @@ def render_main(self, context, animation=False): # -------------------------------- # Loop for all tiles # -------------------------------- - for row in range(0, row_num): - for col in range(0, col_num): + for row in range(row_num): + for col in range(col_num): buffer = bgl.Buffer(bgl.GL_FLOAT, width * height * 4) bgl.glDisable(bgl.GL_SCISSOR_TEST) # if remove this line, get blender screenshot not image bgl.glViewport(0, 0, tile_x, tile_y) @@ -170,7 +170,7 @@ def render_main(self, context, animation=False): if myobj.hide is False: if 'MeasureGenerator' in myobj: # verify visible layer - for x in range(0, 20): + for x in range(20): if myobj.layers[x] is True: if x in layers: op = myobj.MeasureGenerator[0] @@ -216,7 +216,7 @@ def render_main(self, context, animation=False): # -------------------------------- bgl.glFinish() bgl.glReadPixels(0, 0, width, height, bgl.GL_RGBA, bgl.GL_FLOAT, buffer) # read image data - for y in range(0, tile_y): + for y in range(tile_y): # final image pixels position p1 = (y * width * 4) + (row * tile_y * width * 4) + (col * tile_x * 4) p2 = p1 + (tile_x * 4) diff --git a/mesh_carver.py b/mesh_carver.py index aa388a15..16308aa4 100644 --- a/mesh_carver.py +++ b/mesh_carver.py @@ -21,37 +21,44 @@ bl_info = { "name": "Carver MT", "category": "Object", "author": "Pixivore, Cedric LEPILLER, Ted Milker", - "version": (1, 1, 7), - "blender": (2, 77, 0), + "version": (1, 1, 8), + "blender": (2, 79, 2), "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" "Scripts/Modeling/Carver", "description": "Multiple tools to carve or to create objects", - } +} import bpy import bgl import blf import math -import mathutils import sys import random import bmesh +from mathutils import ( + Color, + Euler, + Matrix, + Vector, + Quaternion, +) import bpy_extras from bpy.props import ( - BoolProperty, - EnumProperty, - IntProperty, - StringProperty, - ) + BoolProperty, + IntProperty, + PointerProperty, + StringProperty, +) from bpy_extras import view3d_utils from bpy_extras.view3d_utils import ( - region_2d_to_vector_3d, - region_2d_to_location_3d, - ) + region_2d_to_vector_3d, + region_2d_to_location_3d, +) + Profils = [ ("CTP_4882", - mathutils.Vector((2.61824, -5.56469, 0)), + Vector((2.61824, -5.56469, 0)), [(-1.156501, 0.799282, 0.032334), (-0.967583, 0.838861, 0.032334), (-1.10386, 0.846403, 0.032334), @@ -108,7 +115,7 @@ Profils = [ [38, 39, 47], [34, 32, 35], [39, 46, 47], [43, 34, 35], [35, 38, 47], [47, 42, 35], [32, 33, 35], [42, 40, 43], [36, 37, 39], [42, 43, 35], [44, 45, 47], [46, 44, 47]]), ("CTP_8354", - mathutils.Vector((-0.06267, -2.43829, -0.0)), + Vector((-0.06267, -2.43829, -0.0)), [(-0.534254, -1.0, 0.032334), (-1.0, -0.534254, 0.032334), (-0.654798, -0.98413, 0.032334), @@ -212,7 +219,7 @@ Profils = [ [48, 71, 80, 47], [67, 37, 38, 83], [82, 83, 87, 86], [81, 82, 86, 85], [80, 81, 85, 84], [47, 80, 84, 46], [83, 38, 39, 87]]), ("CTP_5585", - mathutils.Vector((5.0114, -2.4281, 0.0)), + Vector((5.0114, -2.4281, 0.0)), [(-0.490711, -1.0, 0.032334), (-1.0, -0.490711, 0.032334), (1.0, -0.490711, 0.032334), @@ -233,7 +240,7 @@ Profils = [ [[11, 12, 13, 14], [9, 8, 4, 1], [10, 9, 1, 0], [11, 10, 0, 3], [12, 11, 3, 2], [13, 12, 2, 7], [14, 13, 7, 6], [15, 14, 6, 5], [8, 15, 5, 4], [9, 10, 15, 8], [10, 11, 14, 15]]), ("CTP_6960", - mathutils.Vector((-0.11417, 2.48371, -0.0)), + Vector((-0.11417, 2.48371, -0.0)), [(0.0, 1.0, 0.016827), (-0.382683, 0.92388, 0.016827), (-0.707107, 0.707107, 0.016827), @@ -271,7 +278,7 @@ Profils = [ [6, 22, 21, 5], [14, 30, 29, 13], [7, 23, 22, 6], [15, 31, 30, 14], [8, 24, 23, 7], [1, 17, 16, 0], [0, 16, 31, 15], [9, 25, 24, 8], [2, 18, 17, 1], [10, 26, 25, 9]]), ("CTP_5359", - mathutils.Vector((5.50446, 2.41669, -0.0)), + Vector((5.50446, 2.41669, -0.0)), [(0.0, 0.714247, 0.023261), (-0.382683, 0.659879, 0.023261), (-0.707107, 0.505049, 0.023261), @@ -309,7 +316,7 @@ Profils = [ [6, 22, 21, 5], [14, 30, 29, 13], [7, 23, 22, 6], [15, 31, 30, 14], [8, 24, 23, 7], [1, 17, 16, 0], [0, 16, 31, 15], [9, 25, 24, 8], [2, 18, 17, 1], [10, 26, 25, 9]]), ("CTP_5424", - mathutils.Vector((2.61824, 2.34147, 0.0)), + Vector((2.61824, 2.34147, 0.0)), [(1.0, -1.0, 0.032334), (-1.0, 1.0, 0.032334), (1.0, 1.0, 0.032334), @@ -328,7 +335,7 @@ Profils = [ [[3, 0, 2], [10, 9, 2], [2, 1, 4], [2, 4, 13], [5, 3, 2], [6, 5, 2], [2, 13, 12], [2, 12, 11], [7, 6, 2], [8, 7, 2], [2, 11, 10], [9, 8, 2]]), ("CTP_3774", - mathutils.Vector((2.61824, -2.52425, 0.0)), + Vector((2.61824, -2.52425, 0.0)), [(1.0, 0.0, 0.020045), (-1.0, 0.0, 0.020045), (0.31903, -0.664947, 0.020045), @@ -367,7 +374,7 @@ Profils = [ [11, 30, 9, 12], [17, 16, 27, 26], [14, 17, 26, 24], [24, 25, 0, 14], [15, 13, 27, 16], [9, 30, 23, 4], [31, 29, 7, 18], [28, 31, 18, 21], [30, 28, 21, 23]]), ("CTP_4473", - mathutils.Vector((7.31539, 0.0, 0.0)), + Vector((7.31539, 0.0, 0.0)), [(0.24549, -1.0, 0.022454), (-0.24549, -1.0, 0.022454), (-0.24549, 1.0, 0.022454), @@ -383,7 +390,7 @@ Profils = [ ], [[8, 3, 2, 10], [0, 9, 11, 1], [4, 8, 9, 5], [8, 10, 11, 9], [10, 7, 6, 11]]), ("CTP_4003", - mathutils.Vector((4.91276, 0.0, 0.0)), + Vector((4.91276, 0.0, 0.0)), [(-1.0, -1.0, 0.026945), (1.0, -1.0, 0.026945), (-1.0, 1.0, 0.026945), @@ -424,7 +431,7 @@ Profils = [ [23, 25, 31, 29], [24, 23, 29, 30], [25, 22, 28, 31], [26, 22, 15, 20], [10, 27, 33, 5], [31, 28, 3, 11], [33, 30, 13, 14], [29, 31, 11, 12], [5, 33, 14, 1], [30, 29, 12, 13], [32, 28, 22, 26]]), ("CTP_3430", - mathutils.Vector((2.61824, 0.0, 0.0)), + Vector((2.61824, 0.0, 0.0)), [(-1.0, -1.0, 0.032334), (1.0, -1.0, 0.032334), (-1.0, 1.0, 0.032334), @@ -432,7 +439,7 @@ Profils = [ ], [[0, 1, 3, 2]]), ("CTP_7175", - mathutils.Vector((0.0, 0.0, 0.0)), + Vector((0.0, 0.0, 0.0)), [(-1.0, -1.0, 0.032334), (1.0, -1.0, 0.032334), (-1.0, 1.0, 0.032334), @@ -464,33 +471,33 @@ class CarverPrefs(bpy.types.AddonPreferences): bl_idname = __name__ Enable_Tab_01 = BoolProperty( - name="Info", - description="Some general information and settings about the add-on", - default=False - ) + name="Info", + description="Some general information and settings about the add-on", + default=False + ) Enable_Tab_02 = BoolProperty( - name="Hotkeys", - description="List of the shortcuts used during carving", - default=False - ) + name="Hotkeys", + description="List of the shortcuts used during carving", + default=False + ) bpy.types.Scene.Key_Create = StringProperty( - name="Object creation", - description="Object creation", - maxlen=1, - default="C" - ) + name="Object creation", + description="Object creation", + maxlen=1, + default="C" + ) bpy.types.Scene.Key_Update = StringProperty( - name="Auto Bevel Update", - description="Auto Bevel Update", - maxlen=1, - default="A", - ) + name="Auto Bevel Update", + description="Auto Bevel Update", + maxlen=1, + default="A", + ) bpy.types.Scene.Key_Bool = StringProperty( - name="Boolean type", - description="Boolean operation type", - maxlen=1, - default="T", - ) + name="Boolean type", + description="Boolean operation type", + maxlen=1, + default="T", + ) bpy.types.Scene.Key_Brush = StringProperty( name="Brush Mode", description="Brush Mode", @@ -498,167 +505,150 @@ class CarverPrefs(bpy.types.AddonPreferences): default="B", ) bpy.types.Scene.Key_Help = StringProperty( - name="Help display", - description="Help display", - maxlen=1, - default="H", - ) + name="Help display", + description="Help display", + maxlen=1, + default="H", + ) bpy.types.Scene.Key_Instant = StringProperty( - name="Instantiate", - description="Instantiate object", - maxlen=1, - default="I", - ) + name="Instantiate", + description="Instantiate object", + maxlen=1, + default="I", + ) bpy.types.Scene.Key_Close = StringProperty( - name="Close polygonal shape", - description="Close polygonal shape", - maxlen=1, - default="X", - ) + name="Close polygonal shape", + description="Close polygonal shape", + maxlen=1, + default="X", + ) bpy.types.Scene.Key_Apply = StringProperty( - name="Apply operation", - description="Apply operation", - maxlen=1, - default="Q", - ) + name="Apply operation", + description="Apply operation", + maxlen=1, + default="Q", + ) bpy.types.Scene.Key_Scale = StringProperty( - name="Scale object", - description="Scale object", - maxlen=1, - default="S", - ) + name="Scale object", + description="Scale object", + maxlen=1, + default="S", + ) bpy.types.Scene.Key_Gapy = StringProperty( - name="Gap rows", - description="Scale gap between columns", - maxlen=1, - default="J", - ) + name="Gap rows", + description="Scale gap between columns", + maxlen=1, + default="J", + ) bpy.types.Scene.Key_Gapx = StringProperty( - name="Gap columns", - description="Scale gap between columns", - maxlen=1, - default="U", - ) + name="Gap columns", + description="Scale gap between columns", + maxlen=1, + default="U", + ) bpy.types.Scene.Key_Depth = StringProperty( - name="Depth", - description="Cursor depth or solidify pattern", - maxlen=1, - default="D", - ) + name="Depth", + description="Cursor depth or solidify pattern", + maxlen=1, + default="D", + ) bpy.types.Scene.Key_BrushDepth = StringProperty( - name="Brush Depth", - description="Brush depth", - maxlen=1, - default="C", - ) + name="Brush Depth", + description="Brush depth", + maxlen=1, + default="C", + ) bpy.types.Scene.Key_Subadd = StringProperty( - name="Add subdivision", - description="Add subdivision", - maxlen=1, - default="X", - ) + name="Add subdivision", + description="Add subdivision", + maxlen=1, + default="X", + ) bpy.types.Scene.Key_Subrem = StringProperty( - name="Remove subdivision", - description="Remove subdivision", - maxlen=1, - default="W", - ) + name="Remove subdivision", + description="Remove subdivision", + maxlen=1, + default="W", + ) bpy.types.Scene.Key_Randrot = StringProperty( - name="Random rotation", - description="Random rotation", - maxlen=1, - default="R", - ) - bpy.types.Scene.Key_Solver = StringProperty( - name="Solver", - description="Switch between Carve and BMesh Boolean solver\n" - "depending on a specific use case", - maxlen=1, - default="V", - ) + name="Random rotation", + description="Random rotation", + maxlen=1, + default="R", + ) bpy.types.Scene.ProfilePrefix = StringProperty( - name="Profile prefix", - description="Prefix to look for profiles with", - default="Carver_Profile-" - ) - bpy.types.Scene.CarverSolver = EnumProperty( - name="Boolean Solver", - description="Boolean solver to use by default\n", - default="CARVE", - items=( - ('CARVE', 'Carve', "Carve solver, as the legacy one, can handle\n" - "basic coplanar but can often fail with\n" - "non-closed geometry"), - ('BMESH', 'BMesh', "BMesh solver is faster, but cannot handle\n" - "coplanar and self-intersecting geometry") - ) - ) + name="Profile prefix", + description="Prefix to look for profiles with", + default="Carver_Profile-" + ) def draw(self, context): scene = context.scene layout = self.layout - layout.prop(self, "Enable_Tab_01", text="Info and Settings", icon="QUESTION") + icon_1 = "TRIA_RIGHT" if not self.Enable_Tab_01 else "TRIA_DOWN" + box = layout.box() + box.prop(self, "Enable_Tab_01", text="Info and Settings", emboss=False, icon=icon_1) if self.Enable_Tab_01: - layout.label(text="Carver Operator:", icon="LAYER_ACTIVE") - layout.label(text="Select a Mesh Object and press [CTRL]+[SHIFT]+[X] to carve", + box.label(text="Carver Operator:", icon="LAYER_ACTIVE") + box.label(text="Select a Mesh Object and press [CTRL]+[SHIFT]+[X] to carve", icon="LAYER_USED") - layout.label(text="To finish carving press [ESC] or [RIGHT CLICK]", + box.label(text="To finish carving press [ESC] or [RIGHT CLICK]", icon="LAYER_USED") + box.prop(scene, "ProfilePrefix", text="Profile prefix") - layout.prop(scene, "ProfilePrefix", text="Profile prefix") - layout.prop(scene, "CarverSolver", text="Solver") - - layout.prop(self, "Enable_Tab_02", text="Keys", icon="KEYINGSET") + icon_2 = "TRIA_RIGHT" if not self.Enable_Tab_02 else "TRIA_DOWN" + box = layout.box() + box.prop(self, "Enable_Tab_02", text="Keys", emboss=False, icon=icon_2) if self.Enable_Tab_02: - split = layout.split() - col = split.column() + split = box.split(align=True) + box = split.box() + col = box.column(align=True) col.label("Object Creation:") col.prop(scene, "Key_Create", text="") col.label("Auto bevel update:") col.prop(scene, "Key_Update", text="") col.label("Boolean operation type:") col.prop(scene, "Key_Bool", text="") - col.label("Solver:") - col.prop(scene, "Key_Solver", text="") + col.label("Brush Depth:") + col.prop(scene, "Key_BrushDepth", text="") - col = split.column() + box = split.box() + col = box.column(align=True) col.label("Brush Mode:") col.prop(scene, "Key_Brush", text="") col.label("Help display:") col.prop(scene, "Key_Help", text="") col.label("Instantiate object:") col.prop(scene, "Key_Instant", text="") - col.label("Brush Depth:") - col.prop(scene, "Key_BrushDepth", text="") + col.label("Random rotation:") + col.prop(scene, "Key_Randrot", text="") - col = split.column() + box = split.box() + col = box.column(align=True) col.label("Close polygonal shape:") col.prop(scene, "Key_Close", text="") col.label("Apply operation:") col.prop(scene, "Key_Apply", text="") col.label("Scale object:") col.prop(scene, "Key_Scale", text="") + col.label("Subdiv add:") + col.prop(scene, "Key_Subadd", text="") - col = split.column() + box = split.box() + col = box.column(align=True) col.label("Gap rows:") col.prop(scene, "Key_Gapy", text="") col.label("Gap columns:") col.prop(scene, "Key_Gapx", text="") col.label("Depth / Solidify:") col.prop(scene, "Key_Depth", text="") - - col = split.column() - col.label("Subdiv add:") - col.prop(scene, "Key_Subadd", text="") col.label("Subdiv Remove:") col.prop(scene, "Key_Subrem", text="") - col.label("Random rotation:") - col.prop(scene, "Key_Randrot", text="") # Draw Text (Center position) -def DrawCenterText(text, xt, yt, Size, Color, self): +def DrawCenterText(text, xt, yt, Size, colors, self): font_id = 0 # Offset Shadow Sshadow_x = 2 @@ -670,16 +660,16 @@ def DrawCenterText(text, xt, yt, Size, Color, self): blf.draw(font_id, text) blf.position(font_id, xt - blf.dimensions(font_id, text)[0] / 2, yt, 0) - if Color is not None: - mColor = mathutils.Color((Color[0], Color[1], Color[2])) - bgl.glColor4f(mColor.r, mColor.g, mColor.b, 1.0) + if colors is not None: + mcolor = Color((colors[0], colors[1], colors[2])) + bgl.glColor4f(mcolor.r, mcolor.g, mcolor.b, 1.0) else: bgl.glColor4f(1.0, 1.0, 1.0, 1.0) blf.draw(font_id, text) # Draw text (Left position) -def DrawLeftText(text, xt, yt, Size, Color, self): +def DrawLeftText(text, xt, yt, Size, colors, self): font_id = 0 # Offset Shadow Sshadow_x = 2 @@ -690,16 +680,16 @@ def DrawLeftText(text, xt, yt, Size, Color, self): bgl.glColor4f(0.0, 0.0, 0.0, 1.0) blf.draw(font_id, text) blf.position(font_id, xt, yt, 0) - if Color is not None: - mColor = mathutils.Color((Color[0], Color[1], Color[2])) - bgl.glColor4f(mColor.r, mColor.g, mColor.b, 1.0) + if colors is not None: + mcolor = Color((colors[0], colors[1], colors[2])) + bgl.glColor4f(mcolor.r, mcolor.g, mcolor.b, 1.0) else: bgl.glColor4f(1.0, 1.0, 1.0, 1.0) blf.draw(font_id, text) # Draw text (Right position) -def DrawRightText(text, xt, yt, Size, Color, self): +def DrawRightText(text, xt, yt, Size, colors, self): font_id = 0 # Offset Shadow Sshadow_x = 2 @@ -710,9 +700,9 @@ def DrawRightText(text, xt, yt, Size, Color, self): bgl.glColor4f(0.0, 0.0, 0.0, 1.0) blf.draw(font_id, text) blf.position(font_id, xt - blf.dimensions(font_id, text)[0], yt, 0) - if Color is not None: - mColor = mathutils.Color((Color[0], Color[1], Color[2])) - bgl.glColor4f(mColor.r, mColor.g, mColor.b, 1.0) + if colors is not None: + mcolor = Color((colors[0], colors[1], colors[2])) + bgl.glColor4f(mcolor.r, mcolor.g, mcolor.b, 1.0) else: bgl.glColor4f(1.0, 1.0, 1.0, 1.0) blf.draw(font_id, text) @@ -743,28 +733,18 @@ def draw_callback_px(self, context): BooleanMode = "Create" else: if self.ObjectMode or self.ProfileMode: - if self.BoolOps == DIFFERENCE: - BooleanType = "Difference) [T]" - else: - BooleanType = "Union) [T]" - - if self.ObjectMode: - BooleanMode = "Object Brush (" + BooleanType - else: - BooleanMode = "Profil Brush (" + BooleanType + BooleanType = "Difference) [T]" if self.BoolOps == DIFFERENCE else "Union) [T]" + BooleanMode = \ + "Object Brush (" + BooleanType if self.ObjectMode else "Profil Brush (" + BooleanType else: - if (self.shift is False) and (self.ForceRebool is False): - BooleanMode = "Difference" - else: - BooleanMode = "Rebool" + BooleanMode = \ + "Difference" if (self.shift is False) and (self.ForceRebool is False) else "Rebool" UIColor = (0.992, 0.5518, 0.0, 1.0) # Display boolean mode - if region.width >= 850: - DrawCenterText(BooleanMode, xt, yt, 40, UIColor, self) - else: - DrawCenterText(BooleanMode, xt, yt, 20, UIColor, self) + text_size = 40 if region.width >= 850 else 20 + DrawCenterText(BooleanMode, xt, yt, text_size, UIColor, self) # Separator (Line) LineWidth = 75 @@ -790,12 +770,11 @@ def draw_callback_px(self, context): # Variables according to screen size IFontSize = 12 yInterval = 20 - xCmd = 0 yCmd = yt - 30 + if region.width >= 850: IFontSize = 18 yInterval = 25 - xCmd = 100 # Color Color0 = None @@ -816,11 +795,9 @@ def draw_callback_px(self, context): # Depth Cursor TypeStr = "Cursor Depth [" + context.scene.Key_Depth + "] : " - if self.snapCursor: - BoolStr = "(ON)" - else: - BoolStr = "(OFF)" + BoolStr = "(ON)" if self.snapCursor else "(OFF)" OpsStr = TypeStr + BoolStr + TotalWidth = blf.dimensions(font_id, OpsStr)[0] xLeft = region.width / 2 - TotalWidth / 2 xLeftP = xLeft + blf.dimensions(font_id, TypeStr)[0] @@ -830,11 +807,9 @@ def draw_callback_px(self, context): if self.CreateMode is False: # Apply Booleans TypeStr = "Apply Operations [" + context.scene.Key_Apply + "] : " - if self.DontApply: - BoolStr = "(OFF)" - else: - BoolStr = "(ON)" + BoolStr = "(OFF)" if self.DontApply else "(ON)" OpsStr = TypeStr + BoolStr + TotalWidth = blf.dimensions(font_id, OpsStr)[0] xLeft = region.width / 2 - TotalWidth / 2 xLeftP = xLeft + blf.dimensions(font_id, TypeStr)[0] @@ -843,11 +818,9 @@ def draw_callback_px(self, context): # Auto update for bevel TypeStr = "Bevel Update [" + context.scene.Key_Update + "] : " - if self.Auto_BevelUpdate: - BoolStr = "(ON)" - else: - BoolStr = "(OFF)" + BoolStr = "(ON)" if self.Auto_BevelUpdate else "(OFF)" OpsStr = TypeStr + BoolStr + TotalWidth = blf.dimensions(font_id, OpsStr)[0] xLeft = region.width / 2 - TotalWidth / 2 xLeftP = xLeft + blf.dimensions(font_id, TypeStr)[0] @@ -856,10 +829,7 @@ def draw_callback_px(self, context): # Subdivisions if self.CutMode == CIRCLE: - if self.CreateMode is False: - y = yCmd - yInterval * 4 - else: - y = yCmd - yInterval * 2 + y = yCmd - yInterval * 4 if self.CreateMode is False else yCmd - yInterval * 2 TypeStr = "Subdivisions [" + context.scene.Key_Subrem + "][" + context.scene.Key_Subadd + "] : " BoolStr = str((int(360 / self.stepAngle[self.step]))) OpsStr = TypeStr + BoolStr @@ -872,11 +842,9 @@ def draw_callback_px(self, context): else: # INSTANTIATE: TypeStr = "Instantiate [" + context.scene.Key_Instant + "] : " - if self.Instantiate: - BoolStr = "(ON)" - else: - BoolStr = "(OFF)" + BoolStr = "(ON)" if self.Instantiate else "(OFF)" OpsStr = TypeStr + BoolStr + blf.size(font_id, IFontSize, 72) TotalWidth = blf.dimensions(font_id, OpsStr)[0] xLeft = region.width / 2 - TotalWidth / 2 @@ -887,11 +855,9 @@ def draw_callback_px(self, context): # RANDOM ROTATION: if self.alt: TypeStr = "Random Rotation [" + context.scene.Key_Randrot + "] : " - if self.RandomRotation: - BoolStr = "(ON)" - else: - BoolStr = "(OFF)" + BoolStr = "(ON)" if self.RandomRotation else "(OFF)" OpsStr = TypeStr + BoolStr + blf.size(font_id, IFontSize, 72) TotalWidth = blf.dimensions(font_id, OpsStr)[0] xLeft = region.width / 2 - TotalWidth / 2 @@ -911,42 +877,38 @@ def draw_callback_px(self, context): TotalWidth = blf.dimensions(font_id, OpsStr)[0] xLeft = region.width / 2 - TotalWidth / 2 xLeftP = xLeft + blf.dimensions(font_id, TypeStr)[0] - if self.alt: - DrawLeftText(TypeStr, xLeft, yCmd - yInterval * 2, IFontSize, Color0, self) - DrawLeftText(BoolStr, xLeftP, yCmd - yInterval * 2, IFontSize, Color1, self) - else: - DrawLeftText(TypeStr, xLeft, yCmd - yInterval, IFontSize, Color0, self) - DrawLeftText(BoolStr, xLeftP, yCmd - yInterval, IFontSize, Color1, self) + + self_alt_y = 2 if self.alt else 1 + DrawLeftText(TypeStr, xLeft, yCmd - yInterval * self_alt_y, IFontSize, Color0, self) + DrawLeftText(BoolStr, xLeftP, yCmd - yInterval * self_alt_y, IFontSize, Color1, self) # BRUSH DEPTH: if (self.ObjectMode): TypeStr = "Carve Depth [" + context.scene.Key_Depth + "] : " BoolStr = str(round(self.ObjectBrush.data.vertices[0].co.z, 2)) OpsStr = TypeStr + BoolStr + blf.size(font_id, IFontSize, 72) TotalWidth = blf.dimensions(font_id, OpsStr)[0] xLeft = region.width / 2 - TotalWidth / 2 xLeftP = xLeft + blf.dimensions(font_id, TypeStr)[0] - if self.alt: - DrawLeftText(TypeStr, xLeft, yCmd - yInterval * 2, IFontSize, Color0, self) - DrawLeftText(BoolStr, xLeftP, yCmd - yInterval * 2, IFontSize, Color1, self) - else: - DrawLeftText(TypeStr, xLeft, yCmd - yInterval, IFontSize, Color0, self) - DrawLeftText(BoolStr, xLeftP, yCmd - yInterval, IFontSize, Color1, self) + + self_alt_y = 2 if self.alt else 1 + DrawLeftText(TypeStr, xLeft, yCmd - yInterval * self_alt_y, IFontSize, Color0, self) + DrawLeftText(BoolStr, xLeftP, yCmd - yInterval * self_alt_y, IFontSize, Color1, self) TypeStr = "Brush Depth [" + context.scene.Key_BrushDepth + "] : " BoolStr = str(round(self.BrushDepthOffset, 2)) OpsStr = TypeStr + BoolStr + blf.size(font_id, IFontSize, 72) TotalWidth = blf.dimensions(font_id, OpsStr)[0] xLeft = region.width / 2 - TotalWidth / 2 xLeftP = xLeft + blf.dimensions(font_id, TypeStr)[0] - if self.alt: - DrawLeftText(TypeStr, xLeft, yCmd - yInterval * 3, IFontSize, Color0, self) - DrawLeftText(BoolStr, xLeftP, yCmd - yInterval * 3, IFontSize, Color1, self) - else: - DrawLeftText(TypeStr, xLeft, yCmd - yInterval * 2, IFontSize, Color0, self) - DrawLeftText(BoolStr, xLeftP, yCmd - yInterval * 2, IFontSize, Color1, self) + + self_alt_y = 3 if self.alt else 2 + DrawLeftText(TypeStr, xLeft, yCmd - yInterval * self_alt_y, IFontSize, Color0, self) + DrawLeftText(BoolStr, xLeftP, yCmd - yInterval * self_alt_y, IFontSize, Color1, self) bgl.glEnable(bgl.GL_BLEND) if region.width >= 850: @@ -969,21 +931,23 @@ def draw_callback_px(self, context): if self.ObjectMode or self.ProfileMode: if self.ProfileMode: DrawLeftText("[" + context.scene.Key_Brush + "]", xHelp, yHelp + - Help_Interval * 2, Help_FontSize, UIColor, self) + Help_Interval * 2, Help_FontSize, UIColor, self) DrawLeftText(": Object Mode", 150 + t_panel_width, yHelp + - Help_Interval * 2, Help_FontSize, None, self) + Help_Interval * 2, Help_FontSize, None, self) else: DrawLeftText("[" + context.scene.Key_Brush + "]", xHelp, yHelp + - Help_Interval * 2, Help_FontSize, UIColor, self) - DrawLeftText(": Return", 150 + t_panel_width, yHelp + Help_Interval * 2, Help_FontSize, None, self) + Help_Interval * 2, Help_FontSize, UIColor, self) + DrawLeftText(": Return", 150 + t_panel_width, yHelp + + Help_Interval * 2, Help_FontSize, None, self) else: DrawLeftText("[" + context.scene.Key_Brush + "]", xHelp, yHelp + - Help_Interval * 2, Help_FontSize, UIColor, self) + Help_Interval * 2, Help_FontSize, UIColor, self) DrawLeftText(": Profil Brush", 150 + t_panel_width, yHelp + - Help_Interval * 2, Help_FontSize, None, self) - DrawLeftText("[Ctrl + LMB]", xHelp, yHelp - Help_Interval * 6, Help_FontSize, UIColor, self) + Help_Interval * 2, Help_FontSize, None, self) + DrawLeftText("[Ctrl + LMB]", xHelp, yHelp - Help_Interval * 6, + Help_FontSize, UIColor, self) DrawLeftText(": Move Cursor", 150 + t_panel_width, yHelp - - Help_Interval * 6, Help_FontSize, None, self) + Help_Interval * 6, Help_FontSize, None, self) if (self.ObjectMode is False) and (self.ProfileMode is False): if self.CreateMode is False: @@ -994,235 +958,225 @@ def draw_callback_px(self, context): else: DrawLeftText("[" + context.scene.Key_Create + "]", xHelp, yHelp + Help_Interval, Help_FontSize, UIColor, self) - DrawLeftText(": Cut", 150 + t_panel_width, yHelp + Help_Interval, Help_FontSize, None, self) + DrawLeftText(": Cut", 150 + t_panel_width, yHelp + Help_Interval, + Help_FontSize, None, self) if self.CutMode == RECTANGLE: DrawLeftText("MouseMove", xHelp, yHelp, Help_FontSize, UIColor, self) DrawLeftText("[Alt]", xHelp, yHelp - Help_Interval, Help_FontSize, UIColor, self) - DrawLeftText("[" + context.scene.Key_Solver + "]", - xHelp, yHelp - Help_Interval * 2, Help_FontSize, UIColor, self) DrawLeftText(": Dimension", 150 + t_panel_width, yHelp, Help_FontSize, None, self) - DrawLeftText(": Move all", 150 + t_panel_width, yHelp - Help_Interval, Help_FontSize, None, self) - DrawLeftText(": Solver [" + context.scene.CarverSolver + "]", 150 + t_panel_width, - yHelp - Help_Interval * 2, Help_FontSize, None, self) + DrawLeftText(": Move all", 150 + t_panel_width, yHelp - Help_Interval, + Help_FontSize, None, self) if self.CutMode == CIRCLE: DrawLeftText("MouseMove", xHelp, yHelp, Help_FontSize, UIColor, self) DrawLeftText("[Alt]", xHelp, yHelp - Help_Interval, Help_FontSize, UIColor, self) DrawLeftText("[" + context.scene.Key_Subrem + "] [" + context.scene.Key_Subadd + "]", - xHelp, yHelp - Help_Interval * 2, Help_FontSize, UIColor, self) + xHelp, yHelp - Help_Interval * 2, Help_FontSize, UIColor, self) DrawLeftText("[Ctrl]", xHelp, yHelp - Help_Interval * 3, Help_FontSize, UIColor, self) - DrawLeftText("[" + context.scene.Key_Solver + "]", - xHelp, yHelp - Help_Interval * 4, Help_FontSize, UIColor, self) DrawLeftText(": Rotation and Radius", 150 + t_panel_width, yHelp, Help_FontSize, None, self) - DrawLeftText(": Move all", 150 + t_panel_width, yHelp - Help_Interval, Help_FontSize, None, self) + DrawLeftText(": Move all", 150 + t_panel_width, yHelp - Help_Interval, + Help_FontSize, None, self) DrawLeftText(": Subdivision", 150 + t_panel_width, yHelp - - Help_Interval * 2, Help_FontSize, None, self) + Help_Interval * 2, Help_FontSize, None, self) DrawLeftText(": Incremental rotation", 150 + t_panel_width, - yHelp - Help_Interval * 3, Help_FontSize, None, self) - DrawLeftText(": Solver [" + context.scene.CarverSolver + "]", - 150 + t_panel_width, yHelp - Help_Interval * 4, Help_FontSize, None, self) + yHelp - Help_Interval * 3, Help_FontSize, None, self) if self.CutMode == LINE: DrawLeftText("MouseMove", xHelp, yHelp, Help_FontSize, UIColor, self) DrawLeftText("[Alt]", xHelp, yHelp - Help_Interval, Help_FontSize, UIColor, self) DrawLeftText("[Space]", xHelp, yHelp - Help_Interval * 2, Help_FontSize, UIColor, self) DrawLeftText("[Ctrl]", xHelp, yHelp - Help_Interval * 3, Help_FontSize, UIColor, self) - DrawLeftText("[" + context.scene.Key_Solver + "]", - xHelp, yHelp - Help_Interval * 4, Help_FontSize, UIColor, self) DrawLeftText(": Dimension", 150 + t_panel_width, yHelp, Help_FontSize, None, self) - DrawLeftText(": Move all", 150 + t_panel_width, yHelp - Help_Interval, Help_FontSize, None, self) + DrawLeftText(": Move all", 150 + t_panel_width, yHelp - Help_Interval, + Help_FontSize, None, self) DrawLeftText(": Validate", 150 + t_panel_width, yHelp - Help_Interval * 2, Help_FontSize, None, self) DrawLeftText(": Incremental", 150 + t_panel_width, yHelp - - Help_Interval * 3, Help_FontSize, None, self) - DrawLeftText(": Solver [" + context.scene.CarverSolver + "]", - 150 + t_panel_width, yHelp - Help_Interval * 4, Help_FontSize, None, self) + Help_Interval * 3, Help_FontSize, None, self) if self.CreateMode: DrawLeftText("[" + context.scene.Key_Subadd + "]", xHelp, yHelp - - Help_Interval * 4, Help_FontSize, UIColor, self) + Help_Interval * 4, Help_FontSize, UIColor, self) DrawLeftText(": Close geometry", 150 + t_panel_width, yHelp - - Help_Interval * 4, Help_FontSize, None, self) + Help_Interval * 4, Help_FontSize, None, self) else: DrawLeftText("[Space]", xHelp, yHelp + Help_Interval, Help_FontSize, UIColor, self) - DrawLeftText(": Difference", 150 + t_panel_width, yHelp + Help_Interval, Help_FontSize, None, self) + DrawLeftText(": Difference", 150 + t_panel_width, yHelp + Help_Interval, + Help_FontSize, None, self) DrawLeftText("[Shift][Space]", xHelp, yHelp, Help_FontSize, UIColor, self) DrawLeftText(": Rebool", 150 + t_panel_width, yHelp, Help_FontSize, None, self) DrawLeftText("[Alt][Space]", xHelp, yHelp - Help_Interval, Help_FontSize, UIColor, self) - DrawLeftText(": Duplicate", 150 + t_panel_width, yHelp - Help_Interval, Help_FontSize, None, self) + DrawLeftText(": Duplicate", 150 + t_panel_width, yHelp - Help_Interval, + Help_FontSize, None, self) DrawLeftText("[" + context.scene.Key_Scale + "]", xHelp, yHelp - - Help_Interval * 2, Help_FontSize, UIColor, self) - DrawLeftText(": Scale", 150 + t_panel_width, yHelp - Help_Interval * 2, Help_FontSize, None, self) + Help_Interval * 2, Help_FontSize, UIColor, self) + DrawLeftText(": Scale", 150 + t_panel_width, yHelp - Help_Interval * 2, + Help_FontSize, None, self) DrawLeftText("[LMB][Move]", xHelp, yHelp - Help_Interval * 3, Help_FontSize, UIColor, self) - DrawLeftText(": Rotation", 150 + t_panel_width, yHelp - Help_Interval * 3, Help_FontSize, None, self) - DrawLeftText("[Ctrl][LMB][Move]", xHelp, yHelp - Help_Interval * 4, Help_FontSize, UIColor, self) - DrawLeftText(": Step Angle", 150 + t_panel_width, yHelp - Help_Interval * 4, Help_FontSize, None, self) + DrawLeftText(": Rotation", 150 + t_panel_width, yHelp - Help_Interval * 3, + Help_FontSize, None, self) + DrawLeftText("[Ctrl][LMB][Move]", xHelp, yHelp - Help_Interval * 4, + Help_FontSize, UIColor, self) + DrawLeftText(": Step Angle", 150 + t_panel_width, yHelp - Help_Interval * 4, + Help_FontSize, None, self) if self.ProfileMode: DrawLeftText("[" + context.scene.Key_Subadd + "][" + context.scene.Key_Subrem + "]", - xHelp, yHelp - Help_Interval * 5, Help_FontSize, UIColor, self) + xHelp, yHelp - Help_Interval * 5, Help_FontSize, UIColor, self) DrawLeftText(": Previous or Next Profile", 150 + t_panel_width, yHelp - Help_Interval * 5, Help_FontSize, None, self) DrawLeftText("[ARROWS]", xHelp, yHelp - Help_Interval * 6, Help_FontSize, UIColor, self) DrawLeftText(": Create / Delete rows or columns", 150 + t_panel_width, - yHelp - Help_Interval * 6, Help_FontSize, None, self) + yHelp - Help_Interval * 6, Help_FontSize, None, self) DrawLeftText("[" + context.scene.Key_Gapy + "][" + context.scene.Key_Gapx + "]", xHelp, yHelp - Help_Interval * 7, Help_FontSize, UIColor, self) DrawLeftText(": Gap between rows or columns", 150 + t_panel_width, - yHelp - Help_Interval * 7, Help_FontSize, None, self) - DrawLeftText("[" + context.scene.Key_Solver + "]", - xHelp, yHelp - Help_Interval * 8, Help_FontSize, UIColor, self) - DrawLeftText(": Solver [" + context.scene.CarverSolver + "]", - 150 + t_panel_width, yHelp - Help_Interval * 8, Help_FontSize, None, self) + yHelp - Help_Interval * 7, Help_FontSize, None, self) - # Opengl Initialise + # Opengl Initialize bgl.glEnable(bgl.GL_BLEND) bgl.glColor4f(0.512, 0.919, 0.04, 1.0) bgl.glLineWidth(2) - # if context.space_data.region_3d.is_perspective is False: - if 1: - bgl.glEnable(bgl.GL_POINT_SMOOTH) - - bgl.glPointSize(6) + bgl.glEnable(bgl.GL_POINT_SMOOTH) + bgl.glPointSize(6) - if self.ProfileMode: - xrect = region.width - t_panel_width - 80 - yrect = 80 - bgl.glColor4f(0.0, 0.0, 0.0, 0.3) - bgl.glRecti(xrect, yrect, xrect + 60, yrect - 60) - - faces = self.Profils[self.nProfil][3] - vertices = self.Profils[self.nProfil][2] - WidthProfil = 50 - location = mathutils.Vector((region.width - t_panel_width - WidthProfil, 50, 0)) - ProfilScale = 20.0 - bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 0.7) - for f in faces: - if len(f) == 4: - bgl.glBegin(bgl.GL_QUADS) - bgl.glVertex3f(vertices[f[0]][0] * ProfilScale + location.x, vertices[f[0]][1] * - ProfilScale + location.y, vertices[f[0]][2] * ProfilScale + location.z) - bgl.glVertex3f(vertices[f[1]][0] * ProfilScale + location.x, vertices[f[1]][1] * - ProfilScale + location.y, vertices[f[1]][2] * ProfilScale + location.z) - bgl.glVertex3f(vertices[f[2]][0] * ProfilScale + location.x, vertices[f[2]][1] * - ProfilScale + location.y, vertices[f[2]][2] * ProfilScale + location.z) - bgl.glVertex3f(vertices[f[3]][0] * ProfilScale + location.x, vertices[f[3]][1] * - ProfilScale + location.y, vertices[f[3]][2] * ProfilScale + location.z) - bgl.glEnd() - if len(f) == 3: - bgl.glBegin(bgl.GL_TRIANGLES) - bgl.glVertex3f(vertices[f[0]][0] * ProfilScale + location.x, vertices[f[0]][1] * - ProfilScale + location.y, vertices[f[0]][2] * ProfilScale + location.z) - bgl.glVertex3f(vertices[f[1]][0] * ProfilScale + location.x, vertices[f[1]][1] * - ProfilScale + location.y, vertices[f[1]][2] * ProfilScale + location.z) - bgl.glVertex3f(vertices[f[2]][0] * ProfilScale + location.x, vertices[f[2]][1] * - ProfilScale + location.y, vertices[f[2]][2] * ProfilScale + location.z) - bgl.glEnd() - - if self.bDone: - if len(self.mouse_path) > 1: - x0 = self.mouse_path[0][0] - y0 = self.mouse_path[0][1] - x1 = self.mouse_path[1][0] - y1 = self.mouse_path[1][1] - - # Cut Line - if self.CutMode == LINE: - if (self.shift) or (self.CreateMode and self.Closed): - bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 0.5) - - bgl.glBegin(bgl.GL_POLYGON) - for x, y in self.mouse_path: - bgl.glVertex2i(x + self.xpos, y + self.ypos) - bgl.glEnd() - - bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 1.0) - bgl.glBegin(bgl.GL_LINE_STRIP) - for x, y in self.mouse_path: - bgl.glVertex2i(x + self.xpos, y + self.ypos) + if self.ProfileMode: + xrect = region.width - t_panel_width - 80 + yrect = 80 + bgl.glColor4f(0.0, 0.0, 0.0, 0.3) + bgl.glRecti(xrect, yrect, xrect + 60, yrect - 60) + + faces = self.Profils[self.nProfil][3] + vertices = self.Profils[self.nProfil][2] + WidthProfil = 50 + location = Vector((region.width - t_panel_width - WidthProfil, 50, 0)) + ProfilScale = 20.0 + bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 0.7) + for f in faces: + if len(f) == 4: + bgl.glBegin(bgl.GL_QUADS) + bgl.glVertex3f(vertices[f[0]][0] * ProfilScale + location.x, vertices[f[0]][1] * + ProfilScale + location.y, vertices[f[0]][2] * ProfilScale + location.z) + bgl.glVertex3f(vertices[f[1]][0] * ProfilScale + location.x, vertices[f[1]][1] * + ProfilScale + location.y, vertices[f[1]][2] * ProfilScale + location.z) + bgl.glVertex3f(vertices[f[2]][0] * ProfilScale + location.x, vertices[f[2]][1] * + ProfilScale + location.y, vertices[f[2]][2] * ProfilScale + location.z) + bgl.glVertex3f(vertices[f[3]][0] * ProfilScale + location.x, vertices[f[3]][1] * + ProfilScale + location.y, vertices[f[3]][2] * ProfilScale + location.z) bgl.glEnd() - if (self.CreateMode is False) or (self.CreateMode and self.Closed): - bgl.glBegin(bgl.GL_LINE_STRIP) - bgl.glVertex2i(self.mouse_path[len(self.mouse_path) - 1][0] + self.xpos, - self.mouse_path[len(self.mouse_path) - 1][1] + self.ypos) - bgl.glVertex2i(self.mouse_path[0][0] + self.xpos, self.mouse_path[0][1] + self.ypos) - bgl.glEnd() - - bgl.glPointSize(6) - bgl.glBegin(bgl.GL_POINTS) - for x, y in self.mouse_path: - bgl.glVertex2i(x + self.xpos, y + self.ypos) + if len(f) == 3: + bgl.glBegin(bgl.GL_TRIANGLES) + bgl.glVertex3f(vertices[f[0]][0] * ProfilScale + location.x, vertices[f[0]][1] * + ProfilScale + location.y, vertices[f[0]][2] * ProfilScale + location.z) + bgl.glVertex3f(vertices[f[1]][0] * ProfilScale + location.x, vertices[f[1]][1] * + ProfilScale + location.y, vertices[f[1]][2] * ProfilScale + location.z) + bgl.glVertex3f(vertices[f[2]][0] * ProfilScale + location.x, vertices[f[2]][1] * + ProfilScale + location.y, vertices[f[2]][2] * ProfilScale + location.z) bgl.glEnd() - # Cut rectangle - if self.CutMode == RECTANGLE: + if self.bDone: + if len(self.mouse_path) > 1: + x0 = self.mouse_path[0][0] + y0 = self.mouse_path[0][1] + x1 = self.mouse_path[1][0] + y1 = self.mouse_path[1][1] + + # Cut Line + if self.CutMode == LINE: + if (self.shift) or (self.CreateMode and self.Closed): bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 0.5) - # if SHIFT, fill primitive - if self.shift or self.CreateMode: - bgl.glBegin(bgl.GL_QUADS) - bgl.glVertex2i(x0 + self.xpos, y0 + self.ypos) - bgl.glVertex2i(x1 + self.xpos, y0 + self.ypos) - bgl.glVertex2i(x1 + self.xpos, y1 + self.ypos) - bgl.glVertex2i(x0 + self.xpos, y1 + self.ypos) - bgl.glEnd() + bgl.glBegin(bgl.GL_POLYGON) + for x, y in self.mouse_path: + bgl.glVertex2i(x + self.xpos, y + self.ypos) + bgl.glEnd() + bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 1.0) + bgl.glBegin(bgl.GL_LINE_STRIP) + for x, y in self.mouse_path: + bgl.glVertex2i(x + self.xpos, y + self.ypos) + bgl.glEnd() + if (self.CreateMode is False) or (self.CreateMode and self.Closed): bgl.glBegin(bgl.GL_LINE_STRIP) - bgl.glVertex2i(x0 + self.xpos, y0 + self.ypos) - bgl.glVertex2i(x1 + self.xpos, y0 + self.ypos) - bgl.glVertex2i(x1 + self.xpos, y1 + self.ypos) - bgl.glVertex2i(x0 + self.xpos, y1 + self.ypos) - bgl.glVertex2i(x0 + self.xpos, y0 + self.ypos) + bgl.glVertex2i(self.mouse_path[len(self.mouse_path) - 1][0] + self.xpos, + self.mouse_path[len(self.mouse_path) - 1][1] + self.ypos) + bgl.glVertex2i(self.mouse_path[0][0] + self.xpos, self.mouse_path[0][1] + self.ypos) bgl.glEnd() - bgl.glPointSize(6) - bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 1.0) - bgl.glBegin(bgl.GL_POINTS) + bgl.glPointSize(6) + bgl.glBegin(bgl.GL_POINTS) + for x, y in self.mouse_path: + bgl.glVertex2i(x + self.xpos, y + self.ypos) + bgl.glEnd() + + # Cut rectangle + if self.CutMode == RECTANGLE: + bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 0.5) + + # if SHIFT, fill primitive + if self.shift or self.CreateMode: + bgl.glBegin(bgl.GL_QUADS) bgl.glVertex2i(x0 + self.xpos, y0 + self.ypos) bgl.glVertex2i(x1 + self.xpos, y0 + self.ypos) bgl.glVertex2i(x1 + self.xpos, y1 + self.ypos) bgl.glVertex2i(x0 + self.xpos, y1 + self.ypos) bgl.glEnd() - # Circle Cut - if self.CutMode == CIRCLE: - DEG2RAD = 3.14159 / 180 - v0 = mathutils.Vector((self.mouse_path[0][0], self.mouse_path[0][1], 0)) - v1 = mathutils.Vector((self.mouse_path[1][0], self.mouse_path[1][1], 0)) - v0 -= v1 - radius = self.mouse_path[1][0] - self.mouse_path[0][0] - DEG2RAD = 3.14159 / (180.0 / self.stepAngle[self.step]) - if self.ctrl: - self.stepR = (self.mouse_path[1][1] - self.mouse_path[0][1]) / 25 - shift = (3.14159 / (360.0 / 60.0)) * int(self.stepR) - else: - shift = (self.mouse_path[1][1] - self.mouse_path[0][1]) / 50 - - if self.shift or self.CreateMode: - bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 0.5) - bgl.glBegin(bgl.GL_TRIANGLE_FAN) - bgl.glVertex2f(x0 + self.xpos, y0 + self.ypos) - for i in range(0, int(360 / self.stepAngle[self.step])): - degInRad = i * DEG2RAD - bgl.glVertex2f(x0 + self.xpos + math.cos(degInRad + shift) * radius, - y0 + self.ypos + math.sin(degInRad + shift) * radius) - bgl.glVertex2f(x0 + self.xpos + math.cos(0 + shift) * radius, - y0 + self.ypos + math.sin(0 + shift) * radius) - bgl.glEnd() - - bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 1.0) - bgl.glBegin(bgl.GL_LINE_LOOP) + bgl.glBegin(bgl.GL_LINE_STRIP) + bgl.glVertex2i(x0 + self.xpos, y0 + self.ypos) + bgl.glVertex2i(x1 + self.xpos, y0 + self.ypos) + bgl.glVertex2i(x1 + self.xpos, y1 + self.ypos) + bgl.glVertex2i(x0 + self.xpos, y1 + self.ypos) + bgl.glVertex2i(x0 + self.xpos, y0 + self.ypos) + bgl.glEnd() + bgl.glPointSize(6) + + bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 1.0) + bgl.glBegin(bgl.GL_POINTS) + bgl.glVertex2i(x0 + self.xpos, y0 + self.ypos) + bgl.glVertex2i(x1 + self.xpos, y0 + self.ypos) + bgl.glVertex2i(x1 + self.xpos, y1 + self.ypos) + bgl.glVertex2i(x0 + self.xpos, y1 + self.ypos) + bgl.glEnd() + + # Circle Cut + if self.CutMode == CIRCLE: + DEG2RAD = 3.14159 / 180 + v0 = Vector((self.mouse_path[0][0], self.mouse_path[0][1], 0)) + v1 = Vector((self.mouse_path[1][0], self.mouse_path[1][1], 0)) + v0 -= v1 + radius = self.mouse_path[1][0] - self.mouse_path[0][0] + DEG2RAD = 3.14159 / (180.0 / self.stepAngle[self.step]) + if self.ctrl: + self.stepR = (self.mouse_path[1][1] - self.mouse_path[0][1]) / 25 + shift = (3.14159 / (360.0 / 60.0)) * int(self.stepR) + else: + shift = (self.mouse_path[1][1] - self.mouse_path[0][1]) / 50 + + if self.shift or self.CreateMode: + bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 0.5) + bgl.glBegin(bgl.GL_TRIANGLE_FAN) + bgl.glVertex2f(x0 + self.xpos, y0 + self.ypos) for i in range(0, int(360 / self.stepAngle[self.step])): degInRad = i * DEG2RAD bgl.glVertex2f(x0 + self.xpos + math.cos(degInRad + shift) * radius, y0 + self.ypos + math.sin(degInRad + shift) * radius) + bgl.glVertex2f(x0 + self.xpos + math.cos(0 + shift) * radius, + y0 + self.ypos + math.sin(0 + shift) * radius) bgl.glEnd() + bgl.glColor4f(UIColor[0], UIColor[1], UIColor[2], 1.0) + bgl.glBegin(bgl.GL_LINE_LOOP) + for i in range(0, int(360 / self.stepAngle[self.step])): + degInRad = i * DEG2RAD + bgl.glVertex2f(x0 + self.xpos + math.cos(degInRad + shift) * radius, + y0 + self.ypos + math.sin(degInRad + shift) * radius) + bgl.glEnd() + if self.ObjectMode or self.ProfileMode: if self.ShowCursor: region = context.region rv3d = context.space_data.region_3d - view_width = context.region.width if self.ObjectMode: ob = self.ObjectBrush @@ -1233,7 +1187,7 @@ def draw_callback_px(self, context): # 50% alpha, 2 pixel width line bgl.glEnable(bgl.GL_BLEND) - bbox = [mat * mathutils.Vector(b) for b in ob.bound_box] + bbox = [mat * Vector(b) for b in ob.bound_box] if self.shift: bgl.glLineWidth(4) @@ -1260,12 +1214,12 @@ def draw_callback_px(self, context): # Object display if self.qRot is not None: ob.location = self.CurLoc - v = mathutils.Vector() + v = Vector() v.x = v.y = 0.0 v.z = self.BrushDepthOffset ob.location += self.qRot * v - e = mathutils.Euler() + e = Euler() e.x = 0.0 e.y = 0.0 e.z = self.aRotZ / 25.0 @@ -1319,10 +1273,10 @@ def isect_line_plane_v3(p0, p1, p_co, p_no, epsilon=1e-6): # The segment is parallel to plane return None + # ---------------------- # generic math functions - def add_v3v3(v0, v1): return ( v0[0] + v1[0], @@ -1358,21 +1312,19 @@ def mul_v3_fl(v0, f): v0[2] * f, ) -# Cut Square - +# Cut Square def CreateCutSquare(self, context): FAR_LIMIT = 10000.0 # New mesh me = bpy.data.meshes.new('CMT_Square') - # New object ob = bpy.data.objects.new('CMT_Square', me) # Save new object self.CurrentObj = ob - # Scene informations - scene = context.scene + + # Scene information region = context.region rv3d = context.region_data coord = self.mouse_path[0][0], self.mouse_path[0][1] @@ -1382,14 +1334,10 @@ def CreateCutSquare(self, context): if self.snapCursor: PlanePoint = context.scene.cursor_location else: - if self.OpsObj is not None: - PlanePoint = self.OpsObj.location - else: - PlanePoint = mathutils.Vector((0.0, 0.0, 0.0)) + PlanePoint = self.OpsObj.location if self.OpsObj is not None else Vector((0.0, 0.0, 0.0)) PlaneNormal = depthLocation PlaneNormalised = PlaneNormal.normalized() - d = -PlanePoint.x * PlaneNormalised.x - PlanePoint.y * PlaneNormalised.y - PlanePoint.z * PlaneNormalised.z # Link object to scene context.scene.objects.link(ob) @@ -1448,20 +1396,15 @@ def CreateCutLine(self, context): ob = bpy.data.objects.new('CMT_Line', me) self.CurrentObj = ob - scene = context.scene region = context.region rv3d = context.region_data coord = self.mouse_path[0][0], self.mouse_path[0][1] depthLocation = region_2d_to_vector_3d(region, rv3d, coord) self.ViewVector = depthLocation - if self.snapCursor: - PlanePoint = context.scene.cursor_location - else: - PlanePoint = mathutils.Vector((0.0, 0.0, 0.0)) + PlanePoint = context.scene.cursor_location if self.snapCursor else Vector((0.0, 0.0, 0.0)) PlaneNormal = depthLocation PlaneNormalised = PlaneNormal.normalized() - d = -PlanePoint.x * PlaneNormalised.x - PlanePoint.y * PlaneNormalised.y - PlanePoint.z * PlaneNormalised.z context.scene.objects.link(ob) @@ -1473,8 +1416,9 @@ def CreateCutLine(self, context): bLine = False - if (len(self.mouse_path) == 2) or ((len(self.mouse_path) <= 3) and (self.mouse_path[1] == self.mouse_path[2])): - PlanePoint = mathutils.Vector((0.0, 0.0, 0.0)) + if (len(self.mouse_path) == 2) or ((len(self.mouse_path) <= 3) and + (self.mouse_path[1] == self.mouse_path[2])): + PlanePoint = Vector((0.0, 0.0, 0.0)) PlaneNormal = depthLocation PlaneNormalised = PlaneNormal.normalized() # Force rebool @@ -1496,7 +1440,6 @@ def CreateCutLine(self, context): Index += 1 if NbVertices == 1: t_v0 = t_bm.verts.new(loc0) - t_init = t_v0 LocInit = loc0 t_bm.verts.index_update() else: @@ -1504,7 +1447,6 @@ def CreateCutLine(self, context): t_edges = t_bm.edges.new([t_v0, t_v1]) NbVertices = 1 t_v0 = t_v1 - else: for x, y in self.mouse_path: v0 = x + self.xpos, y + self.ypos @@ -1518,7 +1460,6 @@ def CreateCutLine(self, context): NbVertices += 1 if NbVertices == 1: t_v0 = t_bm.verts.new(loc0) - t_init = t_v0 LocInit = loc0 t_bm.verts.index_update() FacesList.append(t_v0) @@ -1554,20 +1495,15 @@ def CreateCutCircle(self, context): ob = bpy.data.objects.new('CMT_Circle', me) self.CurrentObj = ob - scene = context.scene region = context.region rv3d = context.region_data coord = self.mouse_path[0][0], self.mouse_path[0][1] depthLocation = region_2d_to_vector_3d(region, rv3d, coord) self.ViewVector = depthLocation - if self.snapCursor: - PlanePoint = context.scene.cursor_location - else: - PlanePoint = mathutils.Vector((0.0, 0.0, 0.0)) + PlanePoint = context.scene.cursor_location if self.snapCursor else Vector((0.0, 0.0, 0.0)) PlaneNormal = depthLocation PlaneNormalised = PlaneNormal.normalized() - d = -PlanePoint.x * PlaneNormalised.x - PlanePoint.y * PlaneNormalised.y - PlanePoint.z * PlaneNormalised.z context.scene.objects.link(ob) @@ -1576,11 +1512,9 @@ def CreateCutCircle(self, context): x0 = self.mouse_path[0][0] y0 = self.mouse_path[0][1] - x1 = self.mouse_path[1][0] - y1 = self.mouse_path[1][1] - v0 = mathutils.Vector((self.mouse_path[0][0], self.mouse_path[0][1], 0)) - v1 = mathutils.Vector((self.mouse_path[1][0], self.mouse_path[1][1], 0)) + v0 = Vector((self.mouse_path[0][0], self.mouse_path[0][1], 0)) + v1 = Vector((self.mouse_path[1][0], self.mouse_path[1][1], 0)) v0 -= v1 radius = self.mouse_path[1][0] - self.mouse_path[0][0] DEG2RAD = math.pi / (180.0 / self.stepAngle[self.step]) @@ -1594,7 +1528,8 @@ def CreateCutCircle(self, context): FacesList = [] for i in range(0, int(360.0 / self.stepAngle[self.step])): degInRad = i * DEG2RAD - v0 = x0 + self.xpos + math.cos(degInRad + shift) * radius, y0 + self.ypos + math.sin(degInRad + shift) * radius + v0 = x0 + self.xpos + math.cos(degInRad + shift) * radius, \ + y0 + self.ypos + math.sin(degInRad + shift) * radius vec = region_2d_to_vector_3d(region, rv3d, v0) loc0 = region_2d_to_location_3d(region, rv3d, v0, vec) @@ -1607,7 +1542,6 @@ def CreateCutCircle(self, context): FacesList.append(t_v0) t_bm.verts.index_update() - t_face = t_bm.faces.new(FacesList) t_bm.to_mesh(me) @@ -1699,9 +1633,8 @@ def update_bevel(context): obj.select = True context.scene.objects.active = active -# Create bevel - +# Create bevel def CreateBevel(context, CurrentObject): # Save active object SavActive = context.active_object @@ -1752,7 +1685,7 @@ def CreateBevel(context, CurrentObject): context.object.data.use_auto_smooth = True context.object.data.auto_smooth_angle = 1.0471975 - # Remet l'objet actif par défaut + # Restore the active object context.scene.objects.active = SavActive @@ -1767,7 +1700,6 @@ def Picking(context, event): # get the ray from the viewport and mouse view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord) ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord) - ray_target = ray_origin + view_vector def visible_objects_and_duplis(): @@ -1794,8 +1726,7 @@ def Picking(context, event): success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj) if success: return location, normal, face_index - else: - return None, None, None + return None, None, None # cast rays and find the closest object best_length_squared = -1.0 @@ -1820,7 +1751,6 @@ def Picking(context, event): def CreatePrimitive(self, _AngleStep, _radius): - CLRaw = [] Angle = 0.0 self.NbPointsInPrimitive = 0 while(Angle < 360.0): @@ -1838,7 +1768,7 @@ def CreatePrimitive(self, _AngleStep, _radius): def MoveCursor(qRot, location, self): if qRot is not None: self.CLR_C.clear() - vc = mathutils.Vector() + vc = Vector() idx = 0 for i in range(int(len(self.CircleListRaw) / 3)): vc.x = self.CircleListRaw[idx * 3] * self.CRadius @@ -1855,12 +1785,12 @@ def RBenVe(Object, Dir): ObjectV = Object.normalized() DirV = Dir.normalized() cosTheta = ObjectV.dot(DirV) - rotationAxis = mathutils.Vector((0.0, 0.0, 0.0)) + rotationAxis = Vector((0.0, 0.0, 0.0)) if (cosTheta < -1 + 0.001): - v = mathutils.Vector((0.0, 1.0, 0.0)) + v = Vector((0.0, 1.0, 0.0)) rotationAxis = ObjectV.cross(v) rotationAxis = rotationAxis.normalized() - q = mathutils.Quaternion() + q = Quaternion() q.w = 0.0 q.x = rotationAxis.x q.y = rotationAxis.y @@ -1869,7 +1799,7 @@ def RBenVe(Object, Dir): rotationAxis = ObjectV.cross(DirV) s = math.sqrt((1.0 + cosTheta) * 2.0) invs = 1 / s - q = mathutils.Quaternion() + q = Quaternion() q.w = s * 0.5 q.x = rotationAxis.x * invs q.y = rotationAxis.y * invs @@ -1878,7 +1808,6 @@ def RBenVe(Object, Dir): def Pick(context, event, self, ray_max=10000.0): - scene = context.scene region = context.region rv3d = context.region_data coord = event.mouse_region_x, event.mouse_region_y @@ -1893,8 +1822,7 @@ def Pick(context, event, self, ray_max=10000.0): success, hit, normal, face_index = obj.ray_cast(ray_origin_obj, ray_target_obj) if success: return hit, normal, face_index - else: - return None, None, None + return None, None, None best_length_squared = ray_max * ray_max best_obj = None @@ -1914,8 +1842,8 @@ def Pick(context, event, self, ray_max=10000.0): if best_obj is not None: return hits, ns, fs, rotation - else: - return None, None, None + + return None, None, None def SelectObject(self, copyobj): @@ -1939,7 +1867,7 @@ def UndoAdd(self, type, OpsObj): return if type != "DUPLICATE": ob = OpsObj - # Creation du mesh de 'sauvegarde' + # Create the 'backup' mesh bm = bmesh.new() bm.from_mesh(ob.data) @@ -2011,7 +1939,7 @@ def duplicateObject(self): ob_new = bpy.context.active_object ob_new.location = self.CurLoc - v = mathutils.Vector() + v = Vector() v.x = v.y = 0.0 v.z = self.BrushDepthOffset ob_new.location += self.qRot * v @@ -2021,7 +1949,7 @@ def duplicateObject(self): if self.ProfileMode: ob_new.scale = self.ProfileBrush.scale - e = mathutils.Euler() + e = Euler() e.x = e.y = 0.0 e.z = self.aRotZ / 25.0 @@ -2075,7 +2003,10 @@ def update_grid(self, context): # Get the data from the profils or the object if self.ProfileMode: - brush = bpy.data.objects.new(self.Profils[self.nProfil][0], bpy.data.meshes[self.Profils[self.nProfil][0]]) + brush = bpy.data.objects.new( + self.Profils[self.nProfil][0], + bpy.data.meshes[self.Profils[self.nProfil][0]] + ) obj = bpy.data.objects["CT_Profil"] obfaces = brush.data.polygons obverts = brush.data.vertices @@ -2107,7 +2038,7 @@ def update_grid(self, context): # Add random rotation if (self.RandomRotation) and not (self.GridScaleX or self.GridScaleY): - rotmat = mathutils.Matrix.Rotation(math.radians(360 * random.random()), 4, 'Z') + rotmat = Matrix.Rotation(math.radians(360 * random.random()), 4, 'Z') for v in obverts: v.co = v.co * rotmat @@ -2124,47 +2055,25 @@ def update_grid(self, context): mymesh.update(calc_edges=True) # Update data obj.data = mymesh - # Make the the object the active one to remove double + # Make the object active to remove doubles context.scene.objects.active = obj -def boolean_difference(): +def boolean_operation(bool_type="DIFFERENCE"): ActiveObj = bpy.context.active_object + sel_index = 0 if bpy.context.selected_objects[0] != bpy.context.active_object else 1 - if bpy.context.selected_objects[0] != bpy.context.active_object: - bpy.ops.object.modifier_apply(apply_as='DATA', modifier="CT_SOLIDIFY") - BoolMod = ActiveObj.modifiers.new("CT_" + bpy.context.selected_objects[0].name, "BOOLEAN") - BoolMod.object = bpy.context.selected_objects[0] - BoolMod.operation = "DIFFERENCE" - BoolMod.solver = bpy.context.scene.CarverSolver - bpy.context.selected_objects[0].draw_type = 'WIRE' - else: - BoolMod = ActiveObj.modifiers.new("CT_" + bpy.context.selected_objects[1].name, "BOOLEAN") - BoolMod.object = bpy.context.selected_objects[1] - BoolMod.operation = "DIFFERENCE" - BoolMod.solver = bpy.context.scene.CarverSolver - bpy.context.selected_objects[1].draw_type = 'WIRE' - - -def boolean_union(): - ActiveObj = bpy.context.active_object - - if bpy.context.selected_objects[0] != bpy.context.active_object: - BoolMod = ActiveObj.modifiers.new("CT_" + bpy.context.selected_objects[0].name, "BOOLEAN") - BoolMod.object = bpy.context.selected_objects[0] - BoolMod.operation = "UNION" - bpy.context.selected_objects[0].draw_type = 'WIRE' - else: - BoolMod = ActiveObj.modifiers.new("CT_" + bpy.context.selected_objects[1].name, "BOOLEAN") - BoolMod.object = bpy.context.selected_objects[1] - BoolMod.operation = "UNION" - bpy.context.selected_objects[1].draw_type = 'WIRE' + bpy.ops.object.modifier_apply(apply_as='DATA', modifier="CT_SOLIDIFY") + BoolMod = ActiveObj.modifiers.new( + "CT_" + bpy.context.selected_objects[sel_index].name, + "BOOLEAN" + ) + BoolMod.object = bpy.context.selected_objects[sel_index] + BoolMod.operation = bool_type + bpy.context.selected_objects[sel_index].draw_type = 'WIRE' def Rebool(context, self): - SelObj_Name = [] - BoolObj = [] - LastObj = context.active_object Brush = context.selected_objects[0] @@ -2204,12 +2113,10 @@ def Rebool(context, self): m = LastObjectCreated.modifiers.new("CT_INTERSECT", "BOOLEAN") m.operation = "INTERSECT" - m.solver = context.scene.CarverSolver m.object = Brush m = obj.modifiers.new("CT_DIFFERENCE", "BOOLEAN") m.operation = "DIFFERENCE" - m.solver = context.scene.CarverSolver m.object = Brush for mb in LastObj.modifiers: @@ -2262,14 +2169,14 @@ def createMeshFromData(self): if "CT_Profil" not in bpy.data.objects: ob = bpy.data.objects.new("CT_Profil", bpy.data.meshes[self.Profils[self.nProfil][0]]) - ob.location = mathutils.Vector((0.0, 0.0, 0.0)) + ob.location = Vector((0.0, 0.0, 0.0)) # Link object to scene and make active scn = bpy.context.scene scn.objects.link(ob) scn.objects.active = ob ob.select = True - ob.location = mathutils.Vector((10000.0, 0.0, 0.0)) + ob.location = Vector((10000.0, 0.0, 0.0)) ob.draw_type = "WIRE" self.SolidifyPossible = True @@ -2277,25 +2184,38 @@ def createMeshFromData(self): bpy.data.objects["CT_Profil"].data = bpy.data.meshes[self.Profils[self.nProfil][0]] +def Selection_Save_Restore(self): + if "CT_Profil" in bpy.data.objects: + Selection_Save(self) + bpy.ops.object.select_all(action='DESELECT') + bpy.data.objects["CT_Profil"].select = True + bpy.context.scene.objects.active = bpy.data.objects["CT_Profil"] + if bpy.data.objects["CT_Profil"] in self.SavSel: + self.SavSel.remove(bpy.data.objects["CT_Profil"]) + bpy.ops.object.delete(use_global=False) + Selection_Restore(self) + + def Selection_Save(self): + obj_name = getattr(bpy.context.active_object, "name", None) self.SavSel = bpy.context.selected_objects.copy() - self.Sav_ac = bpy.context.active_object + self.Sav_ac = obj_name def Selection_Restore(self): for o in self.SavSel: o.select = True - bpy.context.scene.objects.active = self.Sav_ac + if self.Sav_ac: + bpy.context.scene.objects.active = bpy.data.objects.get(self.Sav_ac, None) # Modal Operator class Carver(bpy.types.Operator): bl_idname = "object.carver" bl_label = "Carver" - bl_description = "Cut or create in object mode" + bl_description = "Cut or create Meshes in Object mode" bl_options = {'REGISTER', 'UNDO'} - # -------------------------------------------------------------------------------------------------- @classmethod def poll(cls, context): ob = None @@ -2306,14 +2226,12 @@ class Carver(bpy.types.Operator): (ob and ob.type == 'MESH' and context.mode == 'OBJECT') or (context.mode == 'OBJECT' and ob is None) or (context.mode == 'EDIT_MESH')) - # -------------------------------------------------------------------------------------------------- - # -------------------------------------------------------------------------------------------------- def modal(self, context, event): PI = 3.14156 - region_types = {'WINDOW', 'UI'} win = context.window + for area in win.screen.areas: if area.type in ('VIEW_3D'): for region in area.regions: @@ -2327,14 +2245,10 @@ class Carver(bpy.types.Operator): return {'PASS_THROUGH'} try: # [Shift] - self.shift = False - if event.shift: - self.shift = True + self.shift = True if event.shift else False # [Ctrl] - self.ctrl = False - if event.ctrl: - self.ctrl = True + self.ctrl = True if event.ctrl else False # [Alt] self.alt = False @@ -2349,7 +2263,7 @@ class Carver(bpy.types.Operator): self.alt = True # [Alt] release if self.InitPosition and self.alt is False: - # Update coordonnee + # Update coordinates for i in range(0, len(self.mouse_path)): l = list(self.mouse_path[i]) l[0] += self.xpos @@ -2417,15 +2331,14 @@ class Carver(bpy.types.Operator): self.CreateGeometry() bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') # Cursor Snap - context.scene.DepthCursor = self.snapCursor + context.scene.mesh_carver.DepthCursor = self.snapCursor # Object Instantiate - context.scene.OInstanciate = self.Instantiate + context.scene.mesh_carver.OInstanciate = self.Instantiate # Random rotation - context.scene.ORandom = self.RandomRotation + context.scene.mesh_carver.ORandom = self.RandomRotation return {'FINISHED'} else: - # Cut self.Cut() UndoListUpdate(self) @@ -2459,28 +2372,13 @@ class Carver(bpy.types.Operator): self.BrushSolidify = False self.CList = self.OB_List - if "CT_Profil" in bpy.data.objects: - Selection_Save(self) - bpy.ops.object.select_all(action='DESELECT') - bpy.data.objects["CT_Profil"].select = True - context.scene.objects.active = bpy.data.objects["CT_Profil"] - bpy.ops.object.delete(use_global=False) - Selection_Restore(self) - - context.scene.nProfile = self.nProfil - + Selection_Save_Restore(self) + context.scene.mesh_carver.nProfile = self.nProfil else: self.ObjectMode = False else: self.BrushSolidify = False - - if "CT_Profil" in bpy.data.objects: - Selection_Save(self) - bpy.ops.object.select_all(action='DESELECT') - bpy.data.objects["CT_Profil"].select = True - context.scene.objects.active = bpy.data.objects["CT_Profil"] - bpy.ops.object.delete(use_global=False) - Selection_Restore(self) + Selection_Save_Restore(self) if self.ProfileMode: createMeshFromData(self) @@ -2496,7 +2394,6 @@ class Carver(bpy.types.Operator): bpy.ops.object.modifier_add(type='SOLIDIFY') context.object.modifiers["Solidify"].name = "CT_SOLIDIFY" - context.object.modifiers["CT_SOLIDIFY"].thickness = 0.1 Selection_Restore(self) @@ -2525,7 +2422,6 @@ class Carver(bpy.types.Operator): Selection_Restore(self) else: - if self.Solidify_Active_Start: Selection_Save(self) self.BrushSolidify = True @@ -2556,12 +2452,6 @@ class Carver(bpy.types.Operator): if event.type == context.scene.Key_Apply and event.value == 'PRESS': self.DontApply = not self.DontApply - if event.type == context.scene.Key_Solver and event.value == 'PRESS': - if context.scene.CarverSolver == "CARVE": - context.scene.CarverSolver = "BMESH" - else: - context.scene.CarverSolver = "CARVE" - # Scale object if event.type == context.scene.Key_Scale and event.value == 'PRESS': if self.ObjectScale is False: @@ -2731,7 +2621,6 @@ class Carver(bpy.types.Operator): self.ProfileBrush.scale.x -= float(self.ascale) / 150.0 self.ProfileBrush.scale.y -= float(self.ascale) / 150.0 self.ProfileBrush.scale.z -= float(self.ascale) / 150.0 - else: if self.LMB: if self.ctrl: @@ -2746,10 +2635,9 @@ class Carver(bpy.types.Operator): vBack = Pick(context, event, self) if vBack[0] is not None: self.ShowCursor = True - NormalObject = mathutils.Vector((0.0, 0.0, 1.0)) + NormalObject = Vector((0.0, 0.0, 1.0)) qR = RBenVe(NormalObject, vBack[1]) self.qRot = vBack[3] * qR - Pos = vBack[0] MoveCursor(qR, vBack[0], self) self.SavCurLoc = vBack[0] if self.ctrl: @@ -2758,13 +2646,13 @@ class Carver(bpy.types.Operator): yEcart = abs(self.SavMousePos.y - self.SavCurLoc.y) zEcart = abs(self.SavMousePos.z - self.SavCurLoc.z) if (xEcart > yEcart) and (xEcart > zEcart): - self.CurLoc = mathutils.Vector( + self.CurLoc = Vector( (vBack[0].x, self.SavMousePos.y, self.SavMousePos.z)) if (yEcart > xEcart) and (yEcart > zEcart): - self.CurLoc = mathutils.Vector( + self.CurLoc = Vector( (self.SavMousePos.x, vBack[0].y, self.SavMousePos.z)) if (zEcart > xEcart) and (zEcart > yEcart): - self.CurLoc = mathutils.Vector( + self.CurLoc = Vector( (self.SavMousePos.x, self.SavMousePos.y, vBack[0].z)) else: self.CurLoc = vBack[0] @@ -2807,7 +2695,7 @@ class Carver(bpy.types.Operator): if self.LMB is False: vBack = Pick(context, event, self) if vBack[0] is not None: - NormalObject = mathutils.Vector((0.0, 0.0, 1.0)) + NormalObject = Vector((0.0, 0.0, 1.0)) self.aqR = RBenVe(NormalObject, vBack[1]) self.qRot = vBack[3] * self.aqR self.am = event.mouse_region_x, event.mouse_region_y @@ -2871,13 +2759,13 @@ class Carver(bpy.types.Operator): self.CreateGeometry() bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') # Depth Cursor - context.scene.DepthCursor = self.snapCursor + context.scene.mesh_carver.DepthCursor = self.snapCursor # Instantiate object - context.scene.OInstanciate = self.Instantiate + context.scene.mesh_carver.OInstanciate = self.Instantiate # Random rotation - context.scene.ORandom = self.RandomRotation + context.scene.mesh_carver.ORandom = self.RandomRotation # Apply operation - context.scene.DontApply = self.DontApply + context.scene.mesh_carver.DontApply = self.DontApply # if Object mode, set intiale state if self.ObjectBrush is not None: @@ -2900,14 +2788,12 @@ class Carver(bpy.types.Operator): Selection_Restore(self) - context.scene.nProfile = self.nProfil + context.scene.mesh_carver.nProfile = self.nProfil return {'FINISHED'} else: - # Cut self.Cut() UndoListUpdate(self) - else: # Line self.mouse_path.append((event.mouse_region_x, event.mouse_region_y)) @@ -2941,17 +2827,16 @@ class Carver(bpy.types.Operator): else: if self.step > 0: self.step -= 1 - # Quit elif event.type in {'RIGHTMOUSE', 'ESC'}: # Depth Cursor - context.scene.DepthCursor = self.snapCursor + context.scene.mesh_carver.DepthCursor = self.snapCursor # Instantiate object - context.scene.OInstanciate = self.Instantiate + context.scene.mesh_carver.OInstanciate = self.Instantiate # Random Rotation - context.scene.ORandom = self.RandomRotation + context.scene.mesh_carver.ORandom = self.RandomRotation # Apply boolean operation - context.scene.DontApply = self.DontApply + context.scene.mesh_carver.DontApply = self.DontApply # Reset Object if self.ObjectBrush is not None: @@ -2971,24 +2856,16 @@ class Carver(bpy.types.Operator): context.scene.objects.active = self.ObjectBrush bpy.ops.object.modifier_remove(modifier="CT_SOLIDIFY") - bpy.ops.object.select_all(action='TOGGLE') Selection_Restore(self) - if "CT_Profil" in bpy.data.objects: - Selection_Save(self) - bpy.ops.object.select_all(action='DESELECT') - bpy.data.objects["CT_Profil"].select = True - context.scene.objects.active = bpy.data.objects["CT_Profil"] - bpy.ops.object.delete(use_global=False) - Selection_Restore(self) - + Selection_Save_Restore(self) context.scene.objects.active = self.CurrentActive - - context.scene.nProfile = self.nProfil + context.scene.mesh_carver.nProfile = self.nProfil bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + # Remove Copy Object Brush if bpy.data.objects.get("CarverBrushCopy") is not None: brush = bpy.data.objects["CarverBrushCopy"] @@ -3003,7 +2880,6 @@ class Carver(bpy.types.Operator): except: print("\n[Carver MT ERROR]\n") - import traceback traceback.print_exc() @@ -3016,242 +2892,246 @@ class Carver(bpy.types.Operator): return {'FINISHED'} - # -------------------------------------------------------------------------------------------------- + def cancel(self, context): + # Note: used to prevent memory leaks on quiting Blender while the modal operator + # is still running, gets called on return {"CANCELLED"} + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') - # -------------------------------------------------------------------------------------------------- def invoke(self, context, event): - if context.area.type == 'VIEW_3D': - if context.mode == 'EDIT_MESH': - bpy.ops.object.mode_set(mode='OBJECT') - - win = context.window - - # Get default patterns - self.Profils = [] - for p in Profils: - self.Profils.append((p[0], p[1], p[2], p[3])) + if context.area.type != 'VIEW_3D': + self.report({'WARNING'}, + "View3D not found or not currently active. Operation Cancelled") + return {'CANCELLED'} - for o in context.scene.objects: - if not o.name.startswith(context.scene.ProfilePrefix): - continue + if context.mode == 'EDIT_MESH': + bpy.ops.object.mode_set(mode='OBJECT') - # In-scene profiles may have changed, remove them to refresh - for m in bpy.data.meshes: - if m.name.startswith(context.scene.ProfilePrefix): - bpy.data.meshes.remove(m) + # test if some other object types are selected that are not meshes + test_selection = True + for obj in context.selected_objects: + if obj.type != "MESH": + test_selection = False + break + if not test_selection: + self.report({'WARNING'}, + "Some selected objects are not of the Mesh type. Operation Cancelled") + return {"CANCELLED"} + + # Get default patterns + self.Profils = [] + for p in Profils: + self.Profils.append((p[0], p[1], p[2], p[3])) + + for o in context.scene.objects: + if not o.name.startswith(context.scene.ProfilePrefix): + continue + + # In-scene profiles may have changed, remove them to refresh + for m in bpy.data.meshes: + if m.name.startswith(context.scene.ProfilePrefix): + bpy.data.meshes.remove(m) + + vertices = [] + for v in o.data.vertices: + vertices.append((v.co.x, v.co.y, v.co.z)) + + faces = [] + for f in o.data.polygons: + face = [] + for v in f.vertices: + face.append(v) + + faces.append(face) + + self.Profils.append( + (o.name, + Vector((o.location.x, o.location.y, o.location.z)), + vertices, faces) + ) - vertices = [] - for v in o.data.vertices: - vertices.append((v.co.x, v.co.y, v.co.z)) + self.nProfil = context.scene.mesh_carver.nProfile + self.MaxProfil = len(self.Profils) - faces = [] - for f in o.data.polygons: - face = [] + # reset selected profile if last profile exceeds length of array + if self.nProfil >= self.MaxProfil: + self.nProfil = context.scene.mesh_carver.nProfile = 0 - for v in f.vertices: - face.append(v) + # Save selection + self.CurrentSelection = context.selected_objects.copy() + self.CurrentActive = context.active_object + self.SavSel = context.selected_objects.copy() + self.Sav_ac = None - faces.append(face) + args = (self, context) - self.Profils.append( - (o.name, - mathutils.Vector((o.location.x, o.location.y, o.location.z)), - vertices, faces) - ) + self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL') - self.nProfil = context.scene.nProfile - self.MaxProfil = len(self.Profils) + self.mouse_path = [(0, 0), (0, 0)] - # reset selected profile if last profile exceeds length of array - if self.nProfil >= self.MaxProfil: - self.nProfil = context.scene.nProfile = 0 + self.shift = False + self.ctrl = False + self.alt = False + self.bDone = False - # Save selection - self.CurrentSelection = context.selected_objects.copy() - self.CurrentActive = context.active_object - self.SavSel = context.selected_objects.copy() - self.Sav_ac = None + self.DontApply = context.scene.mesh_carver.DontApply + self.Auto_BevelUpdate = True - args = (self, context) + # Cut type (Rectangle, Circle, Line) + self.CutMode = 0 + self.BoolOps = DIFFERENCE - self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL') + # Circle variables + self.stepAngle = [2, 4, 5, 6, 9, 10, 15, 20, 30, 40, 45, 60, 72, 90] + self.step = 4 + self.stepRotation = 0 - self.mouse_path = [(0, 0), (0, 0)] + # Primitives Position + self.xpos = 0 + self.ypos = 0 + self.InitPosition = False - self.shift = False - self.ctrl = False - self.alt = False + # Line Increment + self.Increment = 15 + # Close polygonal shape + self.Closed = False - self.bDone = False + # Depth Cursor + self.snapCursor = context.scene.mesh_carver.DepthCursor - self.DontApply = context.scene.DontApply - self.Auto_BevelUpdate = True + # Help + self.AskHelp = False - # Cut type (Rectangle, Circle, Line) - self.CutMode = 0 - self.BoolOps = DIFFERENCE + # Working object + self.OpsObj = context.active_object - # Circle variables - self.stepAngle = [2, 4, 5, 6, 9, 10, 15, 20, 30, 40, 45, 60, 72, 90] - self.step = 4 - self.stepRotation = 0 + # Create mode + self.CreateMode = False + self.ExclusiveCreateMode = False + if len(context.selected_objects) == 0: + self.ExclusiveCreateMode = True + self.CreateMode = True - # Primitives Position - self.xpos = 0 - self.ypos = 0 - self.InitPosition = False + # Rebool forced (cut line) + self.ForceRebool = False - # Line Increment - self.Increment = 15 - # Close polygonal shape - self.Closed = False - - # Depth Cursor - self.snapCursor = context.scene.DepthCursor - - # Help - self.AskHelp = False - - # Working object - self.OpsObj = context.active_object - - # Create mode - self.CreateMode = False - self.ExclusiveCreateMode = False - if len(context.selected_objects) == 0: - self.ExclusiveCreateMode = True - self.CreateMode = True - - # Rebool forced (cut line) - self.ForceRebool = False - - self.ViewVector = mathutils.Vector() - - self.CurrentObj = None - - # Brush - self.BrushSolidify = False - self.WidthSolidify = False - self.CarveDepth = False - self.BrushDepth = False - self.BrushDepthOffset = 0.0 - - self.ObjectScale = False - - self.CircleListRaw = [] - self.CLR_C = [] - self.CurLoc = mathutils.Vector((0.0, 0.0, 0.0)) - self.SavCurLoc = mathutils.Vector((0.0, 0.0, 0.0)) - self.CRadius = 1.0 - CreatePrimitive(self, 10.0, 1.0) - self.VertsList = [] - self.FacesList = [] - - self.am = -1, -1 - self.SavMousePos = None - self.xSavMouse = 0 - - self.ascale = 0 - self.aRotZ = 0 - self.nRotZ = 0 - self.aqR = None - self.qRot = None - - self.RandomRotation = context.scene.ORandom - - self.ShowCursor = True - - self.ObjectMode = False - self.ProfileMode = False - self.Instantiate = context.scene.OInstanciate - - self.ProfileBrush = None - self.ObjectBrush = None - self.InitBrushPosition = None - self.InitBrushScale = None - self.InitBrushQRotation = None - self.InitBrushERotation = None - self.InitBrushARotation = None - - self.ObjectBrush_DT = "WIRE" - self.XRay = False - - # Grid mesh - self.nbcol = 1 - self.nbrow = 1 - self.gapx = 0 - self.gapy = 0 - self.scale_x = 1 - self.scale_y = 1 - - self.GridScaleX = False - self.GridScaleY = False - - if len(context.selected_objects) > 1: - self.ObjectBrush = context.active_object - - # Copy the brush object - ob = bpy.data.objects.new("CarverBrushCopy", context.object.data.copy()) - ob.location = self.ObjectBrush.location - scene = context.scene - scene.objects.link(ob) - scene.update() - - # Get default variables - self.InitBrushPosition = self.ObjectBrush.location.copy() - self.InitBrushScale = self.ObjectBrush.scale.copy() - self.InitBrushQRotation = self.ObjectBrush.rotation_quaternion.copy() - self.InitBrushERotation = self.ObjectBrush.rotation_euler.copy() - self.ObjectBrush_DT = self.ObjectBrush.draw_type - self.XRay = self.ObjectBrush.show_x_ray - # Test if flat object - z = self.ObjectBrush.data.vertices[0].co.z - ErrorMarge = 0.01 - self.Solidify_Active_Start = True - for v in self.ObjectBrush.data.vertices: - if abs(v.co.z - z) > ErrorMarge: - self.Solidify_Active_Start = False - break - self.SolidifyPossible = False - - self.CList = [] - self.OPList = [] - self.RList = [] - self.OB_List = [] - - for ent in context.selected_objects: - if ent != self.ObjectBrush: - self.OB_List.append(ent) - - # Left button - self.LMB = False - - # Undo Variables - self.undo_index = 0 - self.undo_limit = context.user_preferences.edit.undo_steps - self.undo_list = [] - - # Boolean operations type - self.BooleanType = 0 - - self.UList = [] - self.UList_Index = -1 - self.UndoOps = [] - - context.window_manager.modal_handler_add(self) - return {'RUNNING_MODAL'} - else: - self.report({'WARNING'}, "View3D not found, cannot run operator") - return {'CANCELLED'} - # -------------------------------------------------------------------------------------------------- + self.ViewVector = Vector() + self.CurrentObj = None + + # Brush + self.BrushSolidify = False + self.WidthSolidify = False + self.CarveDepth = False + self.BrushDepth = False + self.BrushDepthOffset = 0.0 + + self.ObjectScale = False + + self.CircleListRaw = [] + self.CLR_C = [] + self.CurLoc = Vector((0.0, 0.0, 0.0)) + self.SavCurLoc = Vector((0.0, 0.0, 0.0)) + self.CRadius = 1.0 + CreatePrimitive(self, 10.0, 1.0) + self.VertsList = [] + self.FacesList = [] + + self.am = -1, -1 + self.SavMousePos = None + self.xSavMouse = 0 + + self.ascale = 0 + self.aRotZ = 0 + self.nRotZ = 0 + self.aqR = None + self.qRot = None + + self.RandomRotation = context.scene.mesh_carver.ORandom + + self.ShowCursor = True + self.ObjectMode = False + self.ProfileMode = False + self.Instantiate = context.scene.mesh_carver.OInstanciate + + self.ProfileBrush = None + self.ObjectBrush = None + self.InitBrushPosition = None + self.InitBrushScale = None + self.InitBrushQRotation = None + self.InitBrushERotation = None + self.InitBrushARotation = None + + self.ObjectBrush_DT = "WIRE" + self.XRay = False + + # Grid mesh + self.nbcol = 1 + self.nbrow = 1 + self.gapx = 0 + self.gapy = 0 + self.scale_x = 1 + self.scale_y = 1 + + self.GridScaleX = False + self.GridScaleY = False + + if len(context.selected_objects) > 1: + self.ObjectBrush = context.active_object + + # Copy the brush object + ob = bpy.data.objects.new("CarverBrushCopy", context.object.data.copy()) + ob.location = self.ObjectBrush.location + scene = context.scene + scene.objects.link(ob) + scene.update() + + # Get default variables + self.InitBrushPosition = self.ObjectBrush.location.copy() + self.InitBrushScale = self.ObjectBrush.scale.copy() + self.InitBrushQRotation = self.ObjectBrush.rotation_quaternion.copy() + self.InitBrushERotation = self.ObjectBrush.rotation_euler.copy() + self.ObjectBrush_DT = self.ObjectBrush.draw_type + self.XRay = self.ObjectBrush.show_x_ray + # Test if flat object + z = self.ObjectBrush.data.vertices[0].co.z + ErrorMarge = 0.01 + self.Solidify_Active_Start = True + for v in self.ObjectBrush.data.vertices: + if abs(v.co.z - z) > ErrorMarge: + self.Solidify_Active_Start = False + break + self.SolidifyPossible = False + + self.CList = [] + self.OPList = [] + self.RList = [] + self.OB_List = [] + + for ent in context.selected_objects: + if ent != self.ObjectBrush: + self.OB_List.append(ent) + + # Left button + self.LMB = False + + # Undo Variables + self.undo_index = 0 + self.undo_limit = context.user_preferences.edit.undo_steps + self.undo_list = [] + + # Boolean operations type + self.BooleanType = 0 + + self.UList = [] + self.UList_Index = -1 + self.UndoOps = [] + + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} - # -------------------------------------------------------------------------------------------------- def CreateGeometry(self): context = bpy.context - - region_id = context.region.id - bLocalView = False + for area in context.screen.areas: if area.type == 'VIEW_3D': if area.spaces[0].local_view is not None: @@ -3309,14 +3189,10 @@ class Carver(bpy.types.Operator): self.bDone = False self.mouse_path.clear() self.mouse_path = [(0, 0), (0, 0)] - # -------------------------------------------------------------------------------------------------- - # -------------------------------------------------------------------------------------------------- def Cut(self): context = bpy.context - UNDO = [] - # Local view ? bLocalView = False for area in context.screen.areas: @@ -3358,7 +3234,7 @@ class Carver(bpy.types.Operator): bpy.ops.mesh.normals_make_consistent() bpy.ops.object.mode_set(mode='OBJECT') else: - # Create liste + # Create list if self.ObjectMode: for o in self.CurrentSelection: if o != self.ObjectBrush: @@ -3380,7 +3256,7 @@ class Carver(bpy.types.Operator): if len(context.selected_objects) > 0: bpy.ops.object.select_all(action='TOGGLE') - # Testif intitiale object has bevel + # Test if initial object has bevel BevelAO = False for obj in ActiveObjList: for mb in obj.modifiers: @@ -3407,14 +3283,11 @@ class Carver(bpy.types.Operator): if (self.shift is False) and (self.ForceRebool is False): if self.ObjectMode or self.ProfileMode: if self.BoolOps == UNION: - # Union - boolean_union() + boolean_operation(bool_type="UNION") else: - # Cut object - boolean_difference() + boolean_operation(bool_type="DIFFERENCE") else: - # Cut - boolean_difference() + boolean_operation(bool_type="DIFFERENCE") # Apply booleans if self.DontApply is False: @@ -3487,7 +3360,6 @@ class Carver(bpy.types.Operator): if len(context.selected_objects) > 0: bpy.ops.object.select_all(action='TOGGLE') bpy.data.objects[self.CurrentObj.name].select = True - cname = self.CurrentObj.name bpy.ops.object.delete(use_global=False) else: if self.ObjectMode: @@ -3496,7 +3368,7 @@ class Carver(bpy.types.Operator): if len(context.selected_objects) > 0: bpy.ops.object.select_all(action='TOGGLE') - # Select cutted objects + # Select cut objects for obj in lastSelected: bpy.data.objects[obj.name].select = True @@ -3508,10 +3380,10 @@ class Carver(bpy.types.Operator): if self.Auto_BevelUpdate: update_bevel(context) - # Reselect intiale objects + # Re-select initial objects bpy.ops.object.select_all(action='TOGGLE') if self.ObjectMode: - # Reselect brush + # Re-select brush self.ObjectBrush.select = True for ActiveObj in ActiveObjList: bpy.data.objects[ActiveObj.name].select = True @@ -3535,23 +3407,45 @@ class Carver(bpy.types.Operator): self.ForceRebool = False -classes = ( - Carver, +class CarverProperties(bpy.types.PropertyGroup): + DepthCursor = BoolProperty( + name="DepthCursor", + default=False + ) + OInstanciate = BoolProperty( + name="Obj_Instantiate", + default=False + ) + ORandom = BoolProperty( + name="Random_Rotation", + default=False + ) + DontApply = BoolProperty( + name="Dont_Apply", + default=False ) + nProfile = IntProperty( + name="Num_Profile", + default=0 + ) + addon_keymaps = [] +classes = ( + CarverPrefs, + CarverProperties, + Carver, +) -def register(): - bpy.types.Scene.DepthCursor = BoolProperty(name="DepthCursor", default=False) - bpy.types.Scene.OInstanciate = BoolProperty(name="Obj_Instantiate", default=False) - bpy.types.Scene.ORandom = BoolProperty(name="Random_Rotation", default=False) - bpy.types.Scene.DontApply = BoolProperty(name="Dont_Apply", default=False) - bpy.types.Scene.nProfile = IntProperty(name="Num_Profile", default=0) - bpy.utils.register_class(CarverPrefs) +def register(): + for cls in classes: + bpy.utils.register_class(cls) - bpy.utils.register_class(Carver) + bpy.types.Scene.mesh_carver = PointerProperty( + type=CarverProperties + ) # add keymap entry kcfg = bpy.context.window_manager.keyconfigs.addon if kcfg: @@ -3561,14 +3455,15 @@ def register(): def unregister(): - bpy.utils.unregister_class(CarverPrefs) + for cls in classes: + bpy.utils.unregister_class(cls) # remove keymap entry for km, kmi in addon_keymaps: km.keymap_items.remove(kmi) addon_keymaps.clear() - bpy.utils.unregister_class(Carver) + del bpy.types.Scene.mesh_carver if __name__ == "__main__": diff --git a/mesh_tiny_cad/BIX.py b/mesh_tiny_cad/BIX.py index faac5212..89908f60 100644 --- a/mesh_tiny_cad/BIX.py +++ b/mesh_tiny_cad/BIX.py @@ -92,11 +92,3 @@ class TCLineOnBisection(bpy.types.Operator): def execute(self, context): add_line_to_bisection(self) return {'FINISHED'} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) diff --git a/mesh_tiny_cad/CCEN.py b/mesh_tiny_cad/CCEN.py index f625504b..ef2b5eae 100644 --- a/mesh_tiny_cad/CCEN.py +++ b/mesh_tiny_cad/CCEN.py @@ -157,11 +157,3 @@ class TCCircleCenter(bpy.types.Operator): def execute(self, context): dispatch(context, mode=1) return {'FINISHED'} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) diff --git a/mesh_tiny_cad/CFG.py b/mesh_tiny_cad/CFG.py index ed703a2f..55e0ff9f 100644 --- a/mesh_tiny_cad/CFG.py +++ b/mesh_tiny_cad/CFG.py @@ -74,11 +74,3 @@ def unregister_icons(): for pcoll in icon_collection.values(): bpy.utils.previews.remove(pcoll) icon_collection.clear() - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) diff --git a/mesh_tiny_cad/E2F.py b/mesh_tiny_cad/E2F.py index 8c95f126..25f17e9f 100644 --- a/mesh_tiny_cad/E2F.py +++ b/mesh_tiny_cad/E2F.py @@ -100,11 +100,3 @@ class TCEdgeToFace(bpy.types.Operator): def execute(self, context): extend_vertex(self) return {'FINISHED'} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) diff --git a/mesh_tiny_cad/V2X.py b/mesh_tiny_cad/V2X.py index c230f4a2..c8683297 100644 --- a/mesh_tiny_cad/V2X.py +++ b/mesh_tiny_cad/V2X.py @@ -60,11 +60,3 @@ class TCVert2Intersection(bpy.types.Operator): def execute(self, context): add_vertex_to_intersection() return {'FINISHED'} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) diff --git a/mesh_tiny_cad/VTX.py b/mesh_tiny_cad/VTX.py index 5ad035b2..5cdb2fcf 100644 --- a/mesh_tiny_cad/VTX.py +++ b/mesh_tiny_cad/VTX.py @@ -173,11 +173,3 @@ class TCAutoVTX(bpy.types.Operator): bmesh.update_edit_mesh(me, True) return {'FINISHED'} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) diff --git a/mesh_tiny_cad/XALL.py b/mesh_tiny_cad/XALL.py index 83ecdb52..fc0ed76e 100644 --- a/mesh_tiny_cad/XALL.py +++ b/mesh_tiny_cad/XALL.py @@ -174,11 +174,3 @@ class TCIntersectAllEdges(bpy.types.Operator): print('must be in edit mode') return {'FINISHED'} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) diff --git a/mesh_tiny_cad/__init__.py b/mesh_tiny_cad/__init__.py index 611c67c4..4a9a62f0 100644 --- a/mesh_tiny_cad/__init__.py +++ b/mesh_tiny_cad/__init__.py @@ -50,7 +50,7 @@ if "bpy" in locals(): import bpy -from .CFG import TinyCADProperties +from .CFG import TinyCADProperties, VIEW3D_MT_edit_mesh_tinycad from .CFG import register_icons, unregister_icons from . import VTX, V2X, XALL, BIX, CCEN, E2F @@ -59,10 +59,20 @@ def menu_func(self, context): self.layout.menu("VIEW3D_MT_edit_mesh_tinycad") self.layout.separator() +classes = [ + TinyCADProperties, VIEW3D_MT_edit_mesh_tinycad, + VTX.TCAutoVTX, + XALL.TCIntersectAllEdges, + V2X.TCVert2Intersection, + E2F.TCEdgeToFace, + CCEN.TCCallBackCCEN, CCEN.TCCircleCenter, + BIX.TCLineOnBisection +] def register(): register_icons() - bpy.utils.register_module(__name__) + for cls in classes: + bpy.utils.register_class(cls) bpy.types.Scene.tinycad_props = bpy.props.PointerProperty( name="TinyCAD props", type=TinyCADProperties) bpy.types.VIEW3D_MT_edit_mesh_specials.prepend(menu_func) @@ -70,6 +80,7 @@ def register(): def unregister(): bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_func) - bpy.utils.unregister_module(__name__) + for cls in reversed(classes): + bpy.utils.unregister_class(cls) del bpy.types.Scene.tinycad_props unregister_icons() diff --git a/modules/cycles_shader_compat.py b/modules/cycles_shader_compat.py index 8a967f46..c85ba667 100644 --- a/modules/cycles_shader_compat.py +++ b/modules/cycles_shader_compat.py @@ -19,6 +19,7 @@ # <pep8 compliant> import bpy +import math __all__ = ( "CyclesShaderWrapper", @@ -392,7 +393,7 @@ class CyclesShaderWrapper(): def hardness_value_set(self, value): node = self.node_mix_color_hard - node.inputs["Color1"].default_value = (value,) * 4 + node.inputs["Color1"].default_value = (math.sqrt(max(value, 0.0)),) * 4 def hardness_image_set(self, image): node = self.node_mix_color_hard diff --git a/modules/rna_manual_reference.py b/modules/rna_manual_reference.py index 3cf680cb..a477d3ec 100644 --- a/modules/rna_manual_reference.py +++ b/modules/rna_manual_reference.py @@ -31,6 +31,7 @@ if LANG is not None: url_manual_mapping = ( ("bpy.types.cyclesobjectsettings.use_adaptive_subdivision*", "render/cycles/settings/objects/adaptive_subsurf.html#bpy-types-cyclesobjectsettings-use-adaptive-subdivision"), ("bpy.types.toolsettings.gpencil_stroke_placement_view3d*", "interface/grease_pencil/drawing/introduction.html#bpy-types-toolsettings-gpencil-stroke-placement-view3d"), + ("bpy.types.cyclesrendersettings.offscreen_dicing_scale*", "render/cycles/settings/scene/render/geometry.html#bpy-types-cyclesrendersettings-offscreen-dicing-scale"), ("bpy.types.linestylegeometrymodifier_backbonestretcher*", "render/freestyle/parameter_editor/line_style/modifiers/geometry.html#bpy-types-linestylegeometrymodifier-backbonestretcher"), ("bpy.types.linestylegeometrymodifier_sinusdisplacement*", "render/freestyle/parameter_editor/line_style/modifiers/geometry.html#bpy-types-linestylegeometrymodifier-sinusdisplacement"), ("bpy.types.toolsettings.use_gpencil_continuous_drawing*", "interface/grease_pencil/drawing/introduction.html#bpy-types-toolsettings-use-gpencil-continuous-drawing"), @@ -46,7 +47,7 @@ url_manual_mapping = ( ("bpy.types.linestylegeometrymodifier_guidinglines*", "render/freestyle/parameter_editor/line_style/modifiers/geometry.html#bpy-types-linestylegeometrymodifier-guidinglines"), ("bpy.types.linestylegeometrymodifier_spatialnoise*", "render/freestyle/parameter_editor/line_style/modifiers/geometry.html#bpy-types-linestylegeometrymodifier-spatialnoise"), ("bpy.types.linestylethicknessmodifier_calligraphy*", "render/freestyle/parameter_editor/line_style/modifiers/properties.html#bpy-types-linestylethicknessmodifier-calligraphy"), - ("bpy.types.cyclesrendersettings.max_subdivisions*", "render/cycles/settings/scene/render/integrator.html#bpy-types-cyclesrendersettings-max-subdivisions"), + ("bpy.types.cyclesrendersettings.max_subdivisions*", "render/cycles/settings/scene/render/geometry.html#bpy-types-cyclesrendersettings-max-subdivisions"), ("bpy.types.linestyle*modifier_distancefromcamera*", "render/freestyle/parameter_editor/line_style/modifiers/properties.html#bpy-types-linestyle-modifier-distancefromcamera"), ("bpy.types.linestyle*modifier_distancefromobject*", "render/freestyle/parameter_editor/line_style/modifiers/properties.html#bpy-types-linestyle-modifier-distancefromobject"), ("bpy.types.linestylegeometrymodifier_2dtransform*", "render/freestyle/parameter_editor/line_style/modifiers/geometry.html#bpy-types-linestylegeometrymodifier-2dtransform"), @@ -57,27 +58,31 @@ url_manual_mapping = ( ("bpy.types.cyclesmaterialsettings.displacement*", "render/cycles/materials/settings.html#bpy-types-cyclesmaterialsettings-displacement"), ("bpy.types.linestylegeometrymodifier_blueprint*", "render/freestyle/parameter_editor/line_style/modifiers/geometry.html#bpy-types-linestylegeometrymodifier-blueprint"), ("bpy.types.rendersettings.simplify_subdivision*", "data_system/scenes/properties.html#bpy-types-rendersettings-simplify-subdivision"), + ("bpy.types.cyclesrendersettings.dicing_camera*", "render/cycles/settings/scene/render/geometry.html#bpy-types-cyclesrendersettings-dicing-camera"), ("bpy.types.cyclesrendersettings.texture_limit*", "render/cycles/settings/scene/introduction.html#bpy-types-cyclesrendersettings-texture-limit"), ("bpy.types.linestylegeometrymodifier_2doffset*", "render/freestyle/parameter_editor/line_style/modifiers/geometry.html#bpy-types-linestylegeometrymodifier-2doffset"), ("bpy.types.linestylegeometrymodifier_sampling*", "render/freestyle/parameter_editor/line_style/modifiers/geometry.html#bpy-types-linestylegeometrymodifier-sampling"), ("bpy.types.spaceview3d.show_background_images*", "editors/3dview/properties/background_images.html#bpy-types-spaceview3d-show-background-images"), - ("bpy.types.cyclesrendersettings.*dicing_rate*", "render/cycles/settings/scene/render/integrator.html#bpy-types-cyclesrendersettings-dicing-rate"), + ("bpy.types.cyclesrendersettings.*dicing_rate*", "render/cycles/settings/scene/render/geometry.html#bpy-types-cyclesrendersettings-dicing-rate"), + ("bpy.types.rendersettings.use_file_extension*", "render/output/output.html#bpy-types-rendersettings-use-file-extension"), ("bpy.types.spaceview3d.transform_orientation*", "editors/3dview/object/editing/transform/control/orientations.html#bpy-types-spaceview3d-transform-orientation"), ("bpy.ops.object.constraint_add_with_targets*", "rigging/constraints/interface/adding_removing.html#bpy-ops-object-constraint-add-with-targets"), ("bpy.types.cyclesobjectsettings.dicing_rate*", "render/cycles/settings/objects/adaptive_subsurf.html#bpy-types-cyclesobjectsettings-dicing-rate"), - ("bpy.types.spaceuveditor.use_snap_to_pixels*", "editors/uv_image/uv_editing/layout_editing.html#bpy-types-spaceuveditor-use-snap-to-pixels"), + ("bpy.types.spaceuveditor.use_snap_to_pixels*", "editors/uv_image/uv/editing/layout.html#bpy-types-spaceuveditor-use-snap-to-pixels"), ("bpy.types.linestyle*modifier_curvature_3d*", "render/freestyle/parameter_editor/line_style/modifiers/properties.html#bpy-types-linestyle-modifier-curvature-3d"), + ("bpy.types.rendersettings.use_render_cache*", "render/output/output.html#bpy-types-rendersettings-use-render-cache"), ("bpy.ops.object.anim_transforms_to_deltas*", "editors/3dview/object/editing/transform/clear_apply.html#bpy-ops-object-anim-transforms-to-deltas"), ("bpy.types.compositornodeplanetrackdeform*", "compositing/types/distort/plane_track_deform.html#bpy-types-compositornodeplanetrackdeform"), ("bpy.types.gpencilsculptsettings.lockaxis*", "interface/grease_pencil/drawing/introduction.html#bpy-types-gpencilsculptsettings-lockaxis"), ("bpy.types.linestyle*modifier_alongstroke*", "render/freestyle/parameter_editor/line_style/modifiers/properties.html#bpy-types-linestyle-modifier-alongstroke"), ("bpy.types.linestyle*modifier_creaseangle*", "render/freestyle/parameter_editor/line_style/modifiers/properties.html#bpy-types-linestyle-modifier-creaseangle"), + ("bpy.types.rendersettings.use_placeholder*", "render/output/output.html#bpy-types-rendersettings-use-placeholder"), ("bpy.types.shadernodesubsurfacescattering*", "render/cycles/nodes/types/shaders/sss.html#bpy-types-shadernodesubsurfacescattering"), ("bpy.types.compositornodecolorcorrection*", "compositing/types/color/color_correction.html#bpy-types-compositornodecolorcorrection"), ("bpy.types.compositornodemoviedistortion*", "compositing/types/distort/movie_distortion.html#bpy-types-compositornodemoviedistortion"), ("bpy.types.ffmpegsettings.audio_channels*", "data_system/scenes/properties.html#bpy-types-ffmpegsettings-audio-channels"), - ("bpy.types.fmodifierenvelopecontrolpoint*", "editors/graph_editor/fcurves/fmodifiers.html#bpy-types-fmodifierenvelopecontrolpoint"), - ("bpy.types.spaceuveditor.use_live_unwrap*", "editors/uv_image/uv_editing/layout_editing.html#bpy-types-spaceuveditor-use-live-unwrap"), + ("bpy.types.fmodifierenvelopecontrolpoint*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierenvelopecontrolpoint"), + ("bpy.types.spaceuveditor.use_live_unwrap*", "editors/uv_image/uv/editing/layout.html#bpy-types-spaceuveditor-use-live-unwrap"), ("bpy.types.vertexweightproximitymodifier*", "modeling/modifiers/modify/weight_proximity.html#bpy-types-vertexweightproximitymodifier"), ("bpy.types.compositornodebrightcontrast*", "compositing/types/color/bright_contrast.html#bpy-types-compositornodebrightcontrast"), ("bpy.types.compositornodedoubleedgemask*", "compositing/types/matte/double_edge_mask.html#bpy-types-compositornodedoubleedgemask"), @@ -85,6 +90,8 @@ url_manual_mapping = ( ("bpy.types.material.preview_render_type*", "render/blender_render/materials/properties/preview.html#bpy-types-material-preview-render-type"), ("bpy.types.materialraytracetransparency*", "render/blender_render/materials/properties/transparency.html#bpy-types-materialraytracetransparency"), ("bpy.types.materialsubsurfacescattering*", "render/blender_render/materials/properties/subsurface_scattering.html#bpy-types-materialsubsurfacescattering"), + ("bpy.types.rendersettings.use_overwrite*", "render/output/output.html#bpy-types-rendersettings-use-overwrite"), + ("bpy.types.shadernodevectordisplacement*", "render/cycles/nodes/types/vector/vector_displacement.html#bpy-types-shadernodevectordisplacement"), ("bpy.ops.object.visual_transform_apply*", "editors/3dview/object/editing/transform/clear_apply.html#bpy-ops-object-visual-transform-apply"), ("bpy.types.compositornodebilateralblur*", "compositing/types/filter/bilateral_blur.html#bpy-types-compositornodebilateralblur"), ("bpy.types.compositornodedistancematte*", "compositing/types/matte/distance_key.html#bpy-types-compositornodedistancematte"), @@ -97,12 +104,13 @@ url_manual_mapping = ( ("bpy.types.compositornodecolorbalance*", "compositing/types/color/color_balance.html#bpy-types-compositornodecolorbalance"), ("bpy.types.compositornodekeyingscreen*", "compositing/types/matte/keying_screen.html#bpy-types-compositornodekeyingscreen"), ("bpy.types.dynamicpaintcanvassettings*", "physics/dynamic_paint/canvas.html#bpy-types-dynamicpaintcanvassettings"), - ("bpy.types.fmodifierfunctiongenerator*", "editors/graph_editor/fcurves/fmodifiers.html#bpy-types-fmodifierfunctiongenerator"), + ("bpy.types.fmodifierfunctiongenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierfunctiongenerator"), ("bpy.types.linestyle*modifier_tangent*", "render/freestyle/parameter_editor/line_style/modifiers/properties.html#bpy-types-linestyle-modifier-tangent"), ("bpy.types.movietrackingstabilization*", "editors/movie_clip_editor/tracking/clip/properties/stabilization/index.html#bpy-types-movietrackingstabilization"), ("bpy.types.shadernodeambientocclusion*", "render/cycles/nodes/types/shaders/ao.html#bpy-types-shadernodeambientocclusion"), ("bpy.types.shadernodevolumeabsorption*", "render/cycles/nodes/types/shaders/volume_absorption.html#bpy-types-shadernodevolumeabsorption"), - ("bpy.types.toolsettings.use_uv_sculpt*", "editors/uv_image/uv_editing/uv_sculpt.html#bpy-types-toolsettings-use-uv-sculpt"), + ("bpy.types.shadernodevolumeprincipled*", "render/cycles/nodes/types/shaders/volume_principled.html#bpy-types-shadernodevolumeprincipled"), + ("bpy.types.toolsettings.use_uv_sculpt*", "editors/uv_image/uv/editing/uv_sculpt.html#bpy-types-toolsettings-use-uv-sculpt"), ("bpy.ops.object.duplicates_make_real*", "editors/3dview/object/editing/transform/clear_apply.html#bpy-ops-object-duplicates-make-real"), ("bpy.ops.object.transforms_to_deltas*", "editors/3dview/object/editing/transform/clear_apply.html#bpy-ops-object-transforms-to-deltas"), ("bpy.types.compositornodechromamatte*", "compositing/types/matte/chroma_key.html#bpy-types-compositornodechromamatte"), @@ -112,27 +120,29 @@ url_manual_mapping = ( ("bpy.types.dynamicpaintbrushsettings*", "physics/dynamic_paint/brush.html#bpy-types-dynamicpaintbrushsettings"), ("bpy.types.object.slow_parent_offset*", "editors/3dview/object/properties/relations/extras.html#bpy-types-object-slow-parent-offset"), ("bpy.types.object.use_dupli_vertices*", "editors/3dview/object/properties/duplication/dupliverts.html#bpy-types-object-use-dupli-vertices"), + ("bpy.types.rendersettings.use_border*", "render/output/output.html#bpy-types-rendersettings-use-border"), ("bpy.types.shadernodebsdfanisotropic*", "render/cycles/nodes/types/shaders/anisotropic.html#bpy-types-shadernodebsdfanisotropic"), ("bpy.types.shadernodebsdftranslucent*", "render/cycles/nodes/types/shaders/translucent.html#bpy-types-shadernodebsdftranslucent"), ("bpy.types.shadernodebsdftransparent*", "render/cycles/nodes/types/shaders/transparent.html#bpy-types-shadernodebsdftransparent"), ("bpy.types.shadernodevectortransform*", "render/cycles/nodes/types/vector/transform.html#bpy-types-shadernodevectortransform"), - ("bpy.types.spaceuveditor.lock_bounds*", "editors/uv_image/uv_editing/layout_editing.html#bpy-types-spaceuveditor-lock-bounds"), + ("bpy.types.spaceuveditor.lock_bounds*", "editors/uv_image/uv/editing/layout.html#bpy-types-spaceuveditor-lock-bounds"), ("bpy.ops.object.datalayout_transfer*", "modeling/meshes/editing/data_transfer.html#bpy-ops-object-datalayout-transfer"), ("bpy.ops.object.randomize_transform*", "editors/3dview/object/editing/transform/tools.html#bpy-ops-object-randomize-transform"), ("bpy.types.compositornodebokehimage*", "compositing/types/input/bokeh_image.html#bpy-types-compositornodebokehimage"), ("bpy.types.compositornodecolormatte*", "compositing/types/matte/color_key.html#bpy-types-compositornodecolormatte"), - ("bpy.types.compositornodecolorspill*", "compositing/types/matte/color_spill_key.html#bpy-types-compositornodecolorspill"), + ("bpy.types.compositornodecolorspill*", "compositing/types/matte/color_spill.html#bpy-types-compositornodecolorspill"), ("bpy.types.compositornodehuecorrect*", "compositing/types/color/hue_correct.html#bpy-types-compositornodehuecorrect"), ("bpy.types.compositornodeoutputfile*", "compositing/types/output/file.html#bpy-types-compositornodeoutputfile"), ("bpy.types.compositornodeswitchview*", "compositing/types/converter/switch_view.html#bpy-types-compositornodeswitchview"), ("bpy.types.copytransformsconstraint*", "rigging/constraints/transform/copy_transforms.html#bpy-types-copytransformsconstraint"), ("bpy.types.correctivesmoothmodifier*", "modeling/modifiers/deform/corrective_smooth.html#bpy-types-correctivesmoothmodifier"), - ("bpy.types.cyclesvisibilitysettings*", "render/cycles/settings/scene/render/light_paths.html#bpy-types-cyclesvisibilitysettings"), + ("bpy.types.cyclesvisibilitysettings*", "render/cycles/settings/objects/object_data.html#bpy-types-cyclesvisibilitysettings"), ("bpy.types.linestyle*modifier_noise*", "render/freestyle/parameter_editor/line_style/modifiers/properties.html#bpy-types-linestyle-modifier-noise"), ("bpy.types.maintainvolumeconstraint*", "rigging/constraints/transform/maintain_volume.html#bpy-types-maintainvolumeconstraint"), ("bpy.types.particleinstancemodifier*", "modeling/modifiers/simulate/particle_instance.html#bpy-types-particleinstancemodifier"), ("bpy.types.rigidbodyjointconstraint*", "rigging/constraints/relationship/rigid_body_joint.html#bpy-types-rigidbodyjointconstraint"), ("bpy.types.shadernodebrightcontrast*", "render/cycles/nodes/types/color/bright_contrast.html#bpy-types-shadernodebrightcontrast"), + ("bpy.types.shadernodebsdfprincipled*", "render/cycles/nodes/types/shaders/principled.html#bpy-types-shadernodebsdfprincipled"), ("bpy.types.shadernodebsdfrefraction*", "render/cycles/nodes/types/shaders/refraction.html#bpy-types-shadernodebsdfrefraction"), ("bpy.types.shadernodeoutputmaterial*", "render/cycles/nodes/types/output/material.html#bpy-types-shadernodeoutputmaterial"), ("bpy.types.shadernodetexenvironment*", "render/cycles/nodes/types/textures/environment.html#bpy-types-shadernodetexenvironment"), @@ -164,7 +174,7 @@ url_manual_mapping = ( ("bpy.types.shadernodevolumescatter*", "render/cycles/nodes/types/shaders/volume_scatter.html#bpy-types-shadernodevolumescatter"), ("bpy.types.vertexweightmixmodifier*", "modeling/modifiers/modify/weight_mix.html#bpy-types-vertexweightmixmodifier"), ("bpy.ops.object.constraints_clear*", "rigging/constraints/interface/adding_removing.html#bpy-ops-object-constraints-clear"), - ("bpy.ops.uv.average_islands_scale*", "editors/uv_image/uv_editing/layout_editing.html#bpy-ops-uv-average-islands-scale"), + ("bpy.ops.uv.average_islands_scale*", "editors/uv_image/uv/editing/layout.html#bpy-ops-uv-average-islands-scale"), ("bpy.ops.view3d.edit_mesh_extrude*", "modeling/meshes/editing/duplicating/extrude.html#bpy-ops-view3d-edit-mesh-extrude"), ("bpy.types.brightcontrastmodifier*", "editors/vse/sequencer/properties/modifiers.html#bpy-types-brightcontrastmodifier"), ("bpy.types.camerasolverconstraint*", "rigging/constraints/motion_tracking/camera_solver.html#bpy-types-camerasolverconstraint"), @@ -172,7 +182,7 @@ url_manual_mapping = ( ("bpy.types.compositornodecurvergb*", "compositing/types/color/rgb_curves.html#bpy-types-compositornodecurvergb"), ("bpy.types.compositornodecurvevec*", "compositing/types/vector/vector_curves.html#bpy-types-compositornodecurvevec"), ("bpy.types.compositornodedisplace*", "compositing/types/distort/displace.html#bpy-types-compositornodedisplace"), - ("bpy.types.compositornodelensdist*", "compositing/types/distort/lens.html#bpy-types-compositornodelensdist"), + ("bpy.types.compositornodelensdist*", "compositing/types/distort/lens_distortion.html#bpy-types-compositornodelensdist"), ("bpy.types.compositornodemaprange*", "compositing/types/vector/map_range.html#bpy-types-compositornodemaprange"), ("bpy.types.compositornodemapvalue*", "compositing/types/vector/map_value.html#bpy-types-compositornodemapvalue"), ("bpy.types.compositornodepixelate*", "compositing/types/filter/pixelate.html#bpy-types-compositornodepixelate"), @@ -188,6 +198,7 @@ url_manual_mapping = ( ("bpy.types.object.use_slow_parent*", "editors/3dview/object/properties/relations/extras.html#bpy-types-object-use-slow-parent"), ("bpy.types.objectsolverconstraint*", "rigging/constraints/motion_tracking/object_solver.html#bpy-types-objectsolverconstraint"), ("bpy.types.particlesystemmodifier*", "physics/particles/index.html#bpy-types-particlesystemmodifier"), + ("bpy.types.shadernodedisplacement*", "render/cycles/nodes/types/vector/displacement.html#bpy-types-shadernodedisplacement"), ("bpy.types.shadernodelightfalloff*", "render/cycles/nodes/types/color/light_falloff.html#bpy-types-shadernodelightfalloff"), ("bpy.types.shadernodeparticleinfo*", "render/cycles/nodes/types/input/particle_info.html#bpy-types-shadernodeparticleinfo"), ("bpy.ops.object.constraints_copy*", "rigging/constraints/interface/adding_removing.html#bpy-ops-object-constraints-copy"), @@ -271,8 +282,8 @@ url_manual_mapping = ( ("bpy.ops.object.select_by_type*", "editors/3dview/object/selecting/tools.html#bpy-ops-object-select-by-type"), ("bpy.ops.object.select_grouped*", "editors/3dview/object/selecting/tools.html#bpy-ops-object-select-grouped"), ("bpy.ops.object.select_pattern*", "editors/3dview/object/selecting/tools.html#bpy-ops-object-select-pattern"), - ("bpy.ops.screen.repeat_history*", "interface/undo_and_redo.html#bpy-ops-screen-repeat-history"), - ("bpy.ops.uv.seams_from_islands*", "editors/uv_image/uv_editing/unwrapping/seams.html#bpy-ops-uv-seams-from-islands"), + ("bpy.ops.screen.repeat_history*", "interface/undo_redo.html#bpy-ops-screen-repeat-history"), + ("bpy.ops.uv.seams_from_islands*", "editors/uv_image/uv/editing/unwrapping/seams.html#bpy-ops-uv-seams-from-islands"), ("bpy.types.compositornodedblur*", "compositing/types/filter/directional_blur.html#bpy-types-compositornodedblur"), ("bpy.types.compositornodegamma*", "compositing/types/color/gamma.html#bpy-types-compositornodegamma"), ("bpy.types.compositornodeglare*", "compositing/types/filter/glare.html#bpy-types-compositornodeglare"), @@ -290,7 +301,6 @@ url_manual_mapping = ( ("bpy.types.movietrackingcamera*", "editors/movie_clip_editor/tracking/clip/properties/camera_data.html#bpy-types-movietrackingcamera"), ("bpy.types.object.dupli_frames*", "editors/3dview/object/properties/duplication/dupliframes.html#bpy-types-object-dupli-frames"), ("bpy.types.particledupliweight*", "physics/particles/emitter/vertex_groups.html#bpy-types-particledupliweight"), - ("bpy.types.pointdensitytexture*", "render/blender_render/textures/types/volume/point_density.html#bpy-types-pointdensitytexture"), ("bpy.types.poseboneconstraints*", "rigging/armatures/posing/bone_constraints/index.html#bpy-types-poseboneconstraints"), ("bpy.types.rigidbodyconstraint*", "physics/rigid_body/constraints/index.html#bpy-types-rigidbodyconstraint"), ("bpy.types.shadernodeaddshader*", "render/cycles/nodes/types/shaders/add.html#bpy-types-shadernodeaddshader"), @@ -334,7 +344,7 @@ url_manual_mapping = ( ("bpy.types.compositornodemath*", "compositing/types/converter/math.html#bpy-types-compositornodemath"), ("bpy.types.compositornodetime*", "compositing/types/input/time.html#bpy-types-compositornodetime"), ("bpy.types.fluidfluidsettings*", "physics/fluid/types/fluid_object.html#bpy-types-fluidfluidsettings"), - ("bpy.types.fmodifiergenerator*", "editors/graph_editor/fcurves/fmodifiers.html#bpy-types-fmodifiergenerator"), + ("bpy.types.fmodifiergenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiergenerator"), ("bpy.types.freestylelinestyle*", "render/freestyle/parameter_editor/line_style/index.html#bpy-types-freestylelinestyle"), ("bpy.types.gammacrosssequence*", "editors/vse/sequencer/strips/effects/cross.html#bpy-types-gammacrosssequence"), ("bpy.types.huecorrectmodifier*", "editors/vse/sequencer/properties/modifiers.html#bpy-types-huecorrectmodifier"), @@ -363,11 +373,11 @@ url_manual_mapping = ( ("bpy.ops.curve.primitive*add*", "modeling/curves/primitives.html#bpy-ops-curve-primitive-add"), ("bpy.ops.mesh.duplicate_move*", "modeling/meshes/editing/duplicating/duplicate.html#bpy-ops-mesh-duplicate-move"), ("bpy.ops.object.parent_clear*", "editors/3dview/object/properties/relations/parents.html#bpy-ops-object-parent-clear"), - ("bpy.ops.object.shade_smooth*", "modeling/meshes/editing/smoothing.html#bpy-ops-object-shade-smooth"), + ("bpy.ops.object.shade_smooth*", "modeling/meshes/editing/normals.html#bpy-ops-object-shade-smooth"), ("bpy.ops.transform.push_pull*", "modeling/meshes/editing/transform/push_pull.html#bpy-ops-transform-push-pull"), ("bpy.ops.transform.transform*", "editors/3dview/object/editing/transform/control/orientations.html#bpy-ops-transform-transform"), ("bpy.ops.transform.translate*", "editors/3dview/object/editing/transform/basics.html#bpy-ops-transform-translate"), - ("bpy.ops.uv.minimize_stretch*", "editors/uv_image/uv_editing/layout_editing.html#bpy-ops-uv-minimize-stretch"), + ("bpy.ops.uv.minimize_stretch*", "editors/uv_image/uv/editing/layout.html#bpy-ops-uv-minimize-stretch"), ("bpy.ops.view3d.select_lasso*", "editors/3dview/object/selecting/tools.html#bpy-ops-view3d-select-lasso"), ("bpy.types.alphaoversequence*", "editors/vse/sequencer/strips/effects/alpha_over_under_overdrop.html#bpy-types-alphaoversequence"), ("bpy.types.armatureeditbones*", "rigging/armatures/bones/editing/index.html#bpy-types-armatureeditbones"), @@ -378,8 +388,8 @@ url_manual_mapping = ( ("bpy.types.compositornodergb*", "compositing/types/input/rgb.html#bpy-types-compositornodergb"), ("bpy.types.compositornodesep*", "render/blender_render/textures/nodes/types/color/combine_separate.html#bpy-types-compositornodesep"), ("bpy.types.edgesplitmodifier*", "modeling/modifiers/generate/edge_split.html#bpy-types-edgesplitmodifier"), - ("bpy.types.fmodifierenvelope*", "editors/graph_editor/fcurves/fmodifiers.html#bpy-types-fmodifierenvelope"), - ("bpy.types.freestylesettings*", "render/freestyle/viewmap.html#bpy-types-freestylesettings"), + ("bpy.types.fmodifierenvelope*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierenvelope"), + ("bpy.types.freestylesettings*", "render/freestyle/view_map.html#bpy-types-freestylesettings"), ("bpy.types.material.specular*", "render/blender_render/materials/properties/specular_shaders.html#bpy-types-material-specular"), ("bpy.types.meshcachemodifier*", "modeling/modifiers/modify/mesh_cache.html#bpy-types-meshcachemodifier"), ("bpy.types.movieclipsequence*", "editors/vse/sequencer/strips/clip_mask.html#bpy-types-movieclipsequence"), @@ -394,7 +404,7 @@ url_manual_mapping = ( ("bpy.types.shadernodergbtobw*", "render/cycles/nodes/types/converter/rgb_to_bw.html#bpy-types-shadernodergbtobw"), ("bpy.types.shadernodetangent*", "render/cycles/nodes/types/input/tangent.html#bpy-types-shadernodetangent"), ("bpy.types.shadernodetexwave*", "render/cycles/nodes/types/textures/wave.html#bpy-types-shadernodetexwave"), - ("bpy.types.smokecollsettings*", "physics/smoke/types/collisions.html#bpy-types-smokecollsettings"), + ("bpy.types.smokecollsettings*", "physics/smoke/types/collision.html#bpy-types-smokecollsettings"), ("bpy.types.smokeflowsettings*", "physics/smoke/types/flow_object.html#bpy-types-smokeflowsettings"), ("bpy.types.texturenodebricks*", "render/blender_render/textures/nodes/types/patterns/bricks.html#bpy-types-texturenodebricks"), ("bpy.types.texturenodemixrgb*", "render/blender_render/textures/nodes/types/color/mix_rgb.html#bpy-types-texturenodemixrgb"), @@ -404,20 +414,21 @@ url_manual_mapping = ( ("bpy.types.uvprojectmodifier*", "modeling/modifiers/modify/uv_project.html#bpy-types-uvprojectmodifier"), ("bpy.types.wireframemodifier*", "modeling/modifiers/generate/wireframe.html#bpy-types-wireframemodifier"), ("bpy.types.worldmistsettings*", "render/blender_render/world/mist.html#bpy-types-worldmistsettings"), - ("bpy.ops.mesh.loopcut_slide*", "modeling/meshes/editing/subdividing/loop_subdivide.html#bpy-ops-mesh-loopcut-slide"), + ("bpy.ops.mesh.loopcut_slide*", "modeling/meshes/editing/subdividing/loop.html#bpy-ops-mesh-loopcut-slide"), ("bpy.ops.mesh.primitive*add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-add"), ("bpy.ops.object.join_shapes*", "animation/shape_keys/shape_keys_panel.html#bpy-ops-object-join-shapes"), ("bpy.ops.object.select_less*", "editors/3dview/object/selecting/tools.html#bpy-ops-object-select-less"), ("bpy.ops.object.select_more*", "editors/3dview/object/selecting/tools.html#bpy-ops-object-select-more"), ("bpy.ops.object.track_clear*", "rigging/constraints/interface/adding_removing.html#bpy-ops-object-track-clear"), - ("bpy.ops.screen.repeat_last*", "interface/undo_and_redo.html#bpy-ops-screen-repeat-last"), + ("bpy.ops.screen.repeat_last*", "interface/undo_redo.html#bpy-ops-screen-repeat-last"), ("bpy.ops.transform.tosphere*", "modeling/meshes/editing/transform/to_sphere.html#bpy-ops-transform-tosphere"), ("bpy.types.actionconstraint*", "rigging/constraints/relationship/action.html#bpy-types-actionconstraint"), ("bpy.types.addonpreferences*", "preferences/addons.html#bpy-types-addonpreferences"), ("bpy.types.armaturemodifier*", "modeling/modifiers/deform/armature.html#bpy-types-armaturemodifier"), + ("bpy.types.decimatemodifier*", "modeling/modifiers/generate/decimate.html#bpy-types-decimatemodifier"), ("bpy.types.displacemodifier*", "modeling/modifiers/deform/displace.html#bpy-types-displacemodifier"), ("bpy.types.displaysafeareas*", "render/blender_render/camera/object_data.html#bpy-types-displaysafeareas"), - ("bpy.types.fmodifierstepped*", "editors/graph_editor/fcurves/fmodifiers.html#bpy-types-fmodifierstepped"), + ("bpy.types.fmodifierstepped*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierstepped"), ("bpy.types.freestylelineset*", "render/freestyle/parameter_editor/line_set.html#bpy-types-freestylelineset"), ("bpy.types.material.ambient*", "render/blender_render/materials/properties/shading.html#bpy-types-material-ambient"), ("bpy.types.material.diffuse*", "render/blender_render/materials/properties/diffuse_shaders.html#bpy-types-material-diffuse"), @@ -436,9 +447,11 @@ url_manual_mapping = ( ("bpy.types.shadernodescript*", "render/cycles/nodes/types/script.html#bpy-types-shadernodescript"), ("bpy.types.shadernodetexsky*", "render/cycles/nodes/types/textures/sky.html#bpy-types-shadernodetexsky"), ("bpy.types.softbodymodifier*", "physics/soft_body/index.html#bpy-types-softbodymodifier"), + ("bpy.types.softbodysettings*", "physics/soft_body/settings.html#bpy-types-softbodysettings"), ("bpy.types.solidifymodifier*", "modeling/modifiers/generate/solidify.html#bpy-types-solidifymodifier"), ("bpy.types.spacefilebrowser*", "editors/file_browser/index.html#bpy-types-spacefilebrowser"), ("bpy.types.spacegrapheditor*", "editors/graph_editor/index.html#bpy-types-spacegrapheditor"), + ("bpy.types.spaceimageeditor*", "editors/uv_image/image/index.html#bpy-types-spaceimageeditor"), ("bpy.types.spacelogiceditor*", "editors/logic_editor.html#bpy-types-spacelogiceditor"), ("bpy.types.sphfluidsettings*", "physics/fluid/index.html#bpy-types-sphfluidsettings"), ("bpy.types.subtractsequence*", "editors/vse/sequencer/strips/effects/subtract.html#bpy-types-subtractsequence"), @@ -454,18 +467,18 @@ url_manual_mapping = ( ("bpy.ops.object.parent_set*", "editors/3dview/object/properties/relations/parents.html#bpy-ops-object-parent-set"), ("bpy.ops.object.proxy_make*", "data_system/linked_libraries.html#bpy-ops-object-proxy-make"), ("bpy.ops.object.select_all*", "editors/3dview/object/selecting/tools.html#bpy-ops-object-select-all"), - ("bpy.ops.object.shade_flat*", "modeling/meshes/editing/smoothing.html#bpy-ops-object-shade-flat"), + ("bpy.ops.object.shade_flat*", "modeling/meshes/editing/normals.html#bpy-ops-object-shade-flat"), ("bpy.ops.screen.area_dupli*", "interface/window_system/areas.html#bpy-ops-screen-area-dupli"), - ("bpy.ops.uv.remove_doubles*", "editors/uv_image/uv_editing/layout_editing.html#bpy-ops-uv-remove-doubles"), + ("bpy.ops.uv.remove_doubles*", "editors/uv_image/uv/editing/layout.html#bpy-ops-uv-remove-doubles"), ("bpy.types.backgroundimage*", "editors/3dview/properties/background_images.html#bpy-types-backgroundimage"), ("bpy.types.booleanmodifier*", "modeling/modifiers/generate/booleans.html#bpy-types-booleanmodifier"), ("bpy.types.constraint.mute*", "rigging/constraints/interface/header.html#bpy-types-constraint-mute"), ("bpy.types.explodemodifier*", "modeling/modifiers/simulate/explode.html#bpy-types-explodemodifier"), - ("bpy.types.fcurvemodifiers*", "editors/graph_editor/fcurves/fmodifiers.html#bpy-types-fcurvemodifiers"), + ("bpy.types.fcurvemodifiers*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fcurvemodifiers"), ("bpy.types.floorconstraint*", "rigging/constraints/relationship/floor.html#bpy-types-floorconstraint"), ("bpy.types.fluidmeshvertex*", "physics/fluid/index.html#bpy-types-fluidmeshvertex"), - ("bpy.types.fmodifiercycles*", "editors/graph_editor/fcurves/fmodifiers.html#bpy-types-fmodifiercycles"), - ("bpy.types.fmodifierlimits*", "editors/graph_editor/fcurves/fmodifiers.html#bpy-types-fmodifierlimits"), + ("bpy.types.fmodifiercycles*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiercycles"), + ("bpy.types.fmodifierlimits*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierlimits"), ("bpy.types.gpussaosettings*", "editors/3dview/properties/shading.html#bpy-types-gpussaosettings"), ("bpy.types.latticemodifier*", "modeling/modifiers/deform/lattice.html#bpy-types-latticemodifier"), ("bpy.types.musgravetexture*", "render/blender_render/textures/types/procedural/musgrave.html#bpy-types-musgravetexture"), @@ -474,6 +487,7 @@ url_manual_mapping = ( ("bpy.types.particlehairkey*", "physics/particles/emitter/physics/keyed.html#bpy-types-particlehairkey"), ("bpy.types.pivotconstraint*", "rigging/constraints/relationship/pivot.html#bpy-types-pivotconstraint"), ("bpy.types.rigidbodyobject*", "physics/rigid_body/index.html#bpy-types-rigidbodyobject"), + ("bpy.types.shadernodebevel*", "render/cycles/nodes/types/input/bevel.html#bpy-types-shadernodebevel"), ("bpy.types.shadernodegamma*", "render/cycles/nodes/types/color/gamma.html#bpy-types-shadernodegamma"), ("bpy.types.shadernodegroup*", "render/cycles/nodes/types/groups.html#bpy-types-shadernodegroup"), ("bpy.types.shadernodeuvmap*", "render/cycles/nodes/types/input/uv_map.html#bpy-types-shadernodeuvmap"), @@ -494,7 +508,7 @@ url_manual_mapping = ( ("bpy.types.curvesmodifier*", "editors/vse/sequencer/properties/modifiers.html#bpy-types-curvesmodifier"), ("bpy.types.effectsequence*", "editors/vse/sequencer/properties/filter.html#bpy-types-effectsequence"), ("bpy.types.ffmpegsettings*", "render/output/video.html#bpy-types-ffmpegsettings"), - ("bpy.types.fmodifiernoise*", "editors/graph_editor/fcurves/fmodifiers.html#bpy-types-fmodifiernoise"), + ("bpy.types.fmodifiernoise*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiernoise"), ("bpy.types.gpudofsettings*", "editors/3dview/properties/shading.html#bpy-types-gpudofsettings"), ("bpy.types.materialstrand*", "render/blender_render/materials/properties/strands.html#bpy-types-materialstrand"), ("bpy.types.materialvolume*", "render/blender_render/materials/special_effects/volume.html#bpy-types-materialvolume"), @@ -515,12 +529,12 @@ url_manual_mapping = ( ("bpy.types.voronoitexture*", "render/blender_render/textures/types/procedural/voronoi.html#bpy-types-voronoitexture"), ("bpy.types.walknavigation*", "editors/3dview/navigate/walk_fly.html#bpy-types-walknavigation"), ("bpy.ops.anim.keying_set*", "animation/keyframes/keying_sets.html#bpy-ops-anim-keying-set"), - ("bpy.ops.ed.undo_history*", "interface/undo_and_redo.html#bpy-ops-ed-undo-history"), + ("bpy.ops.ed.undo_history*", "interface/undo_redo.html#bpy-ops-ed-undo-history"), ("bpy.ops.gpencil.convert*", "interface/grease_pencil/convert_to_geometry.html#bpy-ops-gpencil-convert"), ("bpy.ops.object.armature*", "rigging/armatures/index.html#bpy-ops-object-armature"), ("bpy.ops.rigidbody.world*", "physics/rigid_body/world.html#bpy-ops-rigidbody-world"), ("bpy.ops.transform.shear*", "modeling/meshes/editing/transform/shear.html#bpy-ops-transform-shear"), - ("bpy.ops.uv.pack_islands*", "editors/uv_image/uv_editing/layout_editing.html#bpy-ops-uv-pack-islands"), + ("bpy.ops.uv.pack_islands*", "editors/uv_image/uv/editing/layout.html#bpy-ops-uv-pack-islands"), ("bpy.types.armaturebones*", "rigging/armatures/bones/index.html#bpy-types-armaturebones"), ("bpy.types.arraymodifier*", "modeling/modifiers/generate/array.html#bpy-types-arraymodifier"), ("bpy.types.bevelmodifier*", "modeling/modifiers/generate/bevel.html#bpy-types-bevelmodifier"), @@ -534,10 +548,10 @@ url_manual_mapping = ( ("bpy.types.fieldsettings*", "physics/force_fields/index.html#bpy-types-fieldsettings"), ("bpy.types.fluidsettings*", "physics/fluid/index.html#bpy-types-fluidsettings"), ("bpy.types.gpufxsettings*", "editors/3dview/properties/shading.html#bpy-types-gpufxsettings"), - ("bpy.types.imagesequence*", "editors/vse/sequencer/strips/image_movie.html#bpy-types-imagesequence"), + ("bpy.types.imagesequence*", "editors/vse/sequencer/strips/movie_image.html#bpy-types-imagesequence"), ("bpy.types.marbletexture*", "render/blender_render/textures/types/procedural/marble.html#bpy-types-marbletexture"), ("bpy.types.modifier.show*", "modeling/modifiers/introduction.html#bpy-types-modifier-show"), - ("bpy.types.moviesequence*", "editors/vse/sequencer/strips/image_movie.html#bpy-types-moviesequence"), + ("bpy.types.moviesequence*", "editors/vse/sequencer/strips/movie_image.html#bpy-types-moviesequence"), ("bpy.types.movietracking*", "editors/movie_clip_editor/tracking/index.html#bpy-types-movietracking"), ("bpy.types.object.layers*", "editors/3dview/object/properties/relations/layers.html#bpy-types-object-layers"), ("bpy.types.object.parent*", "editors/3dview/object/properties/relations/parents.html#bpy-types-object-parent"), @@ -549,10 +563,10 @@ url_manual_mapping = ( ("bpy.types.sequenceproxy*", "editors/vse/sequencer/properties/proxy_timecode.html#bpy-types-sequenceproxy"), ("bpy.types.shadernodergb*", "render/cycles/nodes/types/input/rgb.html#bpy-types-shadernodergb"), ("bpy.types.smokemodifier*", "physics/smoke/index.html#bpy-types-smokemodifier"), - ("bpy.types.soundsequence*", "editors/vse/sequencer/strips/audio.html#bpy-types-soundsequence"), + ("bpy.types.soundsequence*", "editors/vse/sequencer/strips/sound.html#bpy-types-soundsequence"), ("bpy.types.spaceoutliner*", "editors/outliner.html#bpy-types-spaceoutliner"), ("bpy.types.spacetimeline*", "editors/timeline.html#bpy-types-spacetimeline"), - ("bpy.types.spaceuveditor*", "editors/uv_image/uv_editing/index.html#bpy-types-spaceuveditor"), + ("bpy.types.spaceuveditor*", "editors/uv_image/uv/index.html#bpy-types-spaceuveditor"), ("bpy.types.stuccitexture*", "render/blender_render/textures/types/procedural/stucci.html#bpy-types-stuccitexture"), ("bpy.types.windowmanager*", "interface/index.html#bpy-types-windowmanager"), ("bpy.types.worldlighting*", "render/blender_render/world/ambient_occlusion.html#bpy-types-worldlighting"), @@ -613,8 +627,8 @@ url_manual_mapping = ( ("bpy.ops.object.align*", "editors/3dview/object/editing/transform/tools.html#bpy-ops-object-align"), ("bpy.ops.object.empty*", "modeling/empties.html#bpy-ops-object-empty"), ("bpy.ops.object.quick*", "physics/introduction.html#bpy-ops-object-quick"), - ("bpy.ops.uv.mark_seam*", "editors/uv_image/uv_editing/unwrapping/seams.html#bpy-ops-uv-mark-seam"), - ("bpy.ops.view3d.ruler*", "interface/ruler_and_protractor.html#bpy-ops-view3d-ruler"), + ("bpy.ops.uv.mark_seam*", "editors/uv_image/uv/editing/unwrapping/seams.html#bpy-ops-uv-mark-seam"), + ("bpy.ops.view3d.ruler*", "interface/ruler_protractor.html#bpy-ops-view3d-ruler"), ("bpy.types.areaspaces*", "interface/window_system/areas.html#bpy-types-areaspaces"), ("bpy.types.bpy_struct*", "data_system/custom_properties.html#bpy-types-bpy-struct"), ("bpy.types.compositor*", "compositing/index.html#bpy-types-compositor"), @@ -629,10 +643,11 @@ url_manual_mapping = ( ("bpy.ops.object.join*", "editors/3dview/object/editing/introduction.html#bpy-ops-object-join"), ("bpy.ops.object.text*", "modeling/texts/index.html#bpy-ops-object-text"), ("bpy.ops.view3d.snap*", "editors/3dview/object/editing/transform/control/snap.html#bpy-ops-view3d-snap"), + ("bpy.ops.view3d.view*", "editors/3dview/navigate/introduction.html#bpy-ops-view3d-view"), ("bpy.types.blenddata*", "data_system/data_blocks.html#bpy-types-blenddata"), ("bpy.types.colorramp*", "interface/controls/templates/color_ramp.html#bpy-types-colorramp"), ("bpy.types.dopesheet*", "editors/dope_sheet/index.html#bpy-types-dopesheet"), - ("bpy.types.fmodifier*", "editors/graph_editor/fcurves/fmodifiers.html#bpy-types-fmodifier"), + ("bpy.types.fmodifier*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifier"), ("bpy.types.freestyle*", "render/freestyle/index.html#bpy-types-freestyle"), ("bpy.types.movieclip*", "editors/movie_clip_editor/index.html#bpy-types-movieclip"), ("bpy.types.nodeframe*", "editors/node_editor/nodes/frame.html#bpy-types-nodeframe"), @@ -643,7 +658,7 @@ url_manual_mapping = ( ("bpy.types.uipiemenu*", "interface/controls/buttons/menus.html#bpy-types-uipiemenu"), ("bpy.ops.constraint*", "rigging/constraints/index.html#bpy-ops-constraint"), ("bpy.ops.curve.draw*", "modeling/curves/editing/draw.html#bpy-ops-curve-draw"), - ("bpy.ops.mesh.knife*", "modeling/meshes/editing/subdividing/knife_subdivide.html#bpy-ops-mesh-knife"), + ("bpy.ops.mesh.knife*", "modeling/meshes/editing/subdividing/knife.html#bpy-ops-mesh-knife"), ("bpy.ops.mesh.noise*", "modeling/meshes/editing/transform/noise.html#bpy-ops-mesh-noise"), ("bpy.ops.mesh.screw*", "modeling/meshes/editing/duplicating/screw.html#bpy-ops-mesh-screw"), ("bpy.ops.safe_areas*", "render/blender_render/camera/object_data.html#bpy-ops-safe-areas"), @@ -669,8 +684,8 @@ url_manual_mapping = ( ("bpy.ops.rigidbody*", "physics/rigid_body/index.html#bpy-ops-rigidbody"), ("bpy.ops.sequencer*", "editors/vse/index.html#bpy-ops-sequencer"), ("bpy.ops.transform*", "editors/3dview/object/editing/transform/index.html#bpy-ops-transform"), - ("bpy.ops.uv.stitch*", "editors/uv_image/uv_editing/layout_editing.html#bpy-ops-uv-stitch"), - ("bpy.ops.uv.unwrap*", "editors/uv_image/uv_editing/unwrapping/mapping_types.html#bpy-ops-uv-unwrap"), + ("bpy.ops.uv.stitch*", "editors/uv_image/uv/editing/layout.html#bpy-ops-uv-stitch"), + ("bpy.ops.uv.unwrap*", "editors/uv_image/uv/editing/unwrapping/mapping_types.html#bpy-ops-uv-unwrap"), ("bpy.types.animviz*", "animation/motion_paths.html#bpy-types-animviz"), ("bpy.types.lattice*", "rigging/lattice.html#bpy-types-lattice"), ("bpy.types.library*", "data_system/linked_libraries.html#bpy-types-library"), @@ -683,7 +698,7 @@ url_manual_mapping = ( ("bpy.ops.nla.bake*", "animation/actions.html#bpy-ops-nla-bake"), ("bpy.ops.outliner*", "editors/outliner.html#bpy-ops-outliner"), ("bpy.ops.particle*", "physics/particles/index.html#bpy-ops-particle"), - ("bpy.ops.uv.align*", "editors/uv_image/uv_editing/layout_editing.html#bpy-ops-uv-align"), + ("bpy.ops.uv.align*", "editors/uv_image/uv/editing/layout.html#bpy-ops-uv-align"), ("bpy.ops.wm.addon*", "preferences/addons.html#bpy-ops-wm-addon"), ("bpy.types.action*", "animation/actions.html#bpy-types-action"), ("bpy.types.camera*", "render/blender_render/camera/index.html#bpy-types-camera"), @@ -700,15 +715,15 @@ url_manual_mapping = ( ("bpy.types.window*", "interface/index.html#bpy-types-window"), ("bpy.ops.buttons*", "interface/index.html#bpy-ops-buttons"), ("bpy.ops.console*", "editors/python_console.html#bpy-ops-console"), - ("bpy.ops.ed.redo*", "interface/undo_and_redo.html#bpy-ops-ed-redo"), - ("bpy.ops.ed.undo*", "interface/undo_and_redo.html#bpy-ops-ed-undo"), + ("bpy.ops.ed.redo*", "interface/undo_redo.html#bpy-ops-ed-redo"), + ("bpy.ops.ed.undo*", "interface/undo_redo.html#bpy-ops-ed-undo"), ("bpy.ops.gpencil*", "interface/grease_pencil/index.html#bpy-ops-gpencil"), ("bpy.ops.lattice*", "rigging/lattice.html#bpy-ops-lattice"), ("bpy.ops.poselib*", "rigging/armatures/properties/pose_library.html#bpy-ops-poselib"), ("bpy.ops.ptcache*", "physics/baking.html#bpy-ops-ptcache"), ("bpy.ops.surface*", "modeling/surfaces/index.html#bpy-ops-surface"), ("bpy.ops.texture*", "render/blender_render/textures/index.html#bpy-ops-texture"), - ("bpy.ops.uv.weld*", "editors/uv_image/uv_editing/layout_editing.html#bpy-ops-uv-weld"), + ("bpy.ops.uv.weld*", "editors/uv_image/uv/editing/layout.html#bpy-ops-uv-weld"), ("bpy.types.addon*", "preferences/addons.html#bpy-types-addon"), ("bpy.types.brush*", "sculpt_paint/brush.html#bpy-types-brush"), ("bpy.types.curve*", "modeling/curves/index.html#bpy-types-curve"), @@ -733,8 +748,8 @@ url_manual_mapping = ( ("bpy.ops.screen*", "interface/window_system/screens.html#bpy-ops-screen"), ("bpy.ops.script*", "advanced/scripting/index.html#bpy-ops-script"), ("bpy.ops.sculpt*", "sculpt_paint/sculpting/index.html#bpy-ops-sculpt"), - ("bpy.ops.sketch*", "rigging/armatures/bones/editing/sketching.html#bpy-ops-sketch"), - ("bpy.ops.uv.pin*", "editors/uv_image/uv_editing/layout_editing.html#bpy-ops-uv-pin"), + ("bpy.ops.sketch*", "rigging/armatures/bones/editing/sketching/index.html#bpy-ops-sketch"), + ("bpy.ops.uv.pin*", "editors/uv_image/uv/editing/layout.html#bpy-ops-uv-pin"), ("bpy.ops.view3d*", "editors/3dview/index.html#bpy-ops-view3d"), ("bpy.types.area*", "interface/window_system/areas.html#bpy-types-area"), ("bpy.types.boid*", "physics/particles/emitter/physics/boids.html#bpy-types-boid"), @@ -773,9 +788,9 @@ url_manual_mapping = ( ("bpy.ops.time*", "editors/timeline.html#bpy-ops-time"), ("bpy.types.id*", "data_system/data_blocks.html#bpy-types-id"), ("bpy.ops.nla*", "editors/nla/index.html#bpy-ops-nla"), - ("bpy.ops.ed*", "interface/undo_and_redo.html#bpy-ops-ed"), + ("bpy.ops.ed*", "interface/undo_redo.html#bpy-ops-ed"), ("bpy.ops.ui*", "interface/index.html#bpy-ops-ui"), - ("bpy.ops.uv*", "editors/uv_image/uv_editing/index.html#bpy-ops-uv"), + ("bpy.ops.uv*", "editors/uv_image/uv/index.html#bpy-ops-uv"), ("bpy.ops.wm*", "interface/index.html#bpy-ops-wm"), ("bpy.types*", "index.html#bpy-types"), ("bpy.ops*", "index.html#bpy-ops"), diff --git a/netrender/__init__.py b/netrender/__init__.py index 4186f8b9..aae33c11 100644 --- a/netrender/__init__.py +++ b/netrender/__init__.py @@ -21,13 +21,13 @@ bl_info = { "name": "Network Renderer", "author": "Martin Poirier", - "version": (1, 8), + "version": (1, 8, 1), "blender": (2, 60, 0), "location": "Render > Engine > Network Render", "description": "Distributed rendering for Blender", "warning": "Stable but still work in progress", - "wiki_url": "http://wiki.blender.org/index.php/Doc:2.5/Manual/" - "Render/Engines/Netrender", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" + "Scripts/Render/Net_render", "category": "Render", } diff --git a/node_wrangler.py b/node_wrangler.py index 9b225af1..24e096c8 100644 --- a/node_wrangler.py +++ b/node_wrangler.py @@ -3161,7 +3161,7 @@ class NWSelectParentChildren(Operator, NWBase): class NWDetachOutputs(Operator, NWBase): - """Detach outputs of selected node leaving inluts liked""" + """Detach outputs of selected node leaving inputs linked""" bl_idname = "node.nw_detach_outputs" bl_label = "Detach Outputs" bl_options = {'REGISTER', 'UNDO'} diff --git a/object_boolean_tools.py b/object_boolean_tools.py index 3c5b104b..df082abc 100644 --- a/object_boolean_tools.py +++ b/object_boolean_tools.py @@ -21,8 +21,8 @@ bl_info = { "name": "Bool Tool", "author": "Vitor Balbio, Mikhail Rachinskiy, TynkaTopi, Meta-Androcto", - "version": (0, 3, 8), - "blender": (2, 78, 0), + "version": (0, 3, 9), + "blender": (2, 79, 2), "location": "View3D > Toolshelf", "description": "Bool Tool Hotkey: Ctrl Shift B", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" @@ -143,7 +143,6 @@ def Operation(context, _operation): prefs = bpy.context.user_preferences.addons[__name__].preferences useWire = prefs.use_wire - solver = prefs.solver for selObj in bpy.context.selected_objects: if selObj != context.active_object and (selObj.type == "MESH" or selObj.type == "CURVE"): @@ -180,7 +179,6 @@ def Operation(context, _operation): clone["BoolToolRoot"] = True newMod = actObj.modifiers.new("BTool_" + selObj.name, "BOOLEAN") newMod.object = selObj - newMod.solver = solver if _operation == "SLICE": newMod.operation = "INTERSECT" else: @@ -615,19 +613,6 @@ class BTool_Slice(Operator): class Auto_Boolean: - solver = EnumProperty( - name="Boolean Solver", - description="Specify solver for boolean operation", - items=(('BMESH', "BMesh", "BMesh solver is faster, but less stable " - "and cannot handle coplanar geometry"), - ('CARVE', "Carve", "Carve solver is slower, but more stable " - "and can handle simple cases of coplanar geometry")), - options={'SKIP_SAVE'}, - ) - - def __init__(self): - self.solver = bpy.context.user_preferences.addons[__name__].preferences.solver - def objects_prepare(self): for ob in bpy.context.selected_objects: if ob.type != 'MESH': @@ -663,7 +648,6 @@ class Auto_Boolean: md = obj.modifiers.new("Auto Boolean", 'BOOLEAN') md.show_viewport = False md.operation = mode - md.solver = self.solver md.object = ob bpy.ops.object.modifier_apply(modifier="Auto Boolean") @@ -1288,15 +1272,6 @@ class PREFS_BoolTool_Props(AddonPreferences): default="Tools", update=update_panels, ) - solver = EnumProperty( - name="Boolean Solver", - items=(('BMESH', "BMesh", "BMesh solver is faster, but less stable " - "and cannot handle coplanar geometry"), - ('CARVE', "Carve", "Carve solver is slower, but more stable " - "and can handle simple cases of coplanar geometry")), - default='BMESH', - description="Specify solver for boolean operations", - ) Enable_Tab_01 = BoolProperty( default=False ) @@ -1309,29 +1284,23 @@ class PREFS_BoolTool_Props(AddonPreferences): col = split.column() col.label(text="Tab Category:") col = split.column() - colrow = col.row() - colrow.prop(self, "category", text="") - - split = layout.split(percentage=split_percent) - col = split.column() - col.label("Boolean Solver:") - col = split.column() - colrow = col.row() - colrow.prop(self, "solver", expand=True) + col.prop(self, "category", text="") split = layout.split(percentage=split_percent) col = split.column() col.label("Experimental Features:") col = split.column() - colrow = col.row(align=True) - colrow.prop(self, "fast_transform", toggle=True) - colrow.prop(self, "use_wire", text="Use Wire Instead Of Bbox", toggle=True) + col.prop(self, "fast_transform") + col.prop(self, "use_wire", text="Use Wire Instead Of Bbox") + layout.separator() + """ # EXPERIMENTAL col.prop(self, "make_vertex_groups") col.prop(self, "make_boundary") """ + layout.prop(self, "Enable_Tab_01", text="Hot Keys", icon="KEYINGSET") if self.Enable_Tab_01: row = layout.row() diff --git a/object_print3d_utils/mesh_helpers.py b/object_print3d_utils/mesh_helpers.py index ca6e0716..e1386b84 100644 --- a/object_print3d_utils/mesh_helpers.py +++ b/object_print3d_utils/mesh_helpers.py @@ -279,3 +279,19 @@ def object_merge(context, objects): # return new object return base_base + + +def face_is_distorted(ele, angle_distort): + no = ele.normal + angle_fn = no.angle + + for loop in ele.loops: + loopno = loop.calc_normal() + + if loopno.dot(no) < 0.0: + loopno.negate() + + if angle_fn(loopno, 1000.0) > angle_distort: + return True + + return False diff --git a/object_print3d_utils/operators.py b/object_print3d_utils/operators.py index 8dcdf211..be8c323e 100644 --- a/object_print3d_utils/operators.py +++ b/object_print3d_utils/operators.py @@ -20,6 +20,8 @@ # All Operator +import array + import bpy from bpy.types import Operator from bpy.props import ( @@ -129,8 +131,6 @@ class MESH_OT_Print3D_Check_Solid(Operator): @staticmethod def main_check(obj, info): - import array - bm = mesh_helpers.bmesh_copy_from_object(obj, transform=False, triangulate=False) edges_non_manifold = array.array('i', (i for i, ele in enumerate(bm.edges) @@ -173,7 +173,6 @@ class MESH_OT_Print3D_Check_Degenerate(Operator): @staticmethod def main_check(obj, info): - import array scene = bpy.context.scene print_3d = scene.print_3d threshold = print_3d.threshold_zero @@ -202,27 +201,17 @@ class MESH_OT_Print3D_Check_Distorted(Operator): @staticmethod def main_check(obj, info): - import array - scene = bpy.context.scene print_3d = scene.print_3d angle_distort = print_3d.angle_distort - def face_is_distorted(ele): - no = ele.normal - angle_fn = no.angle - for loop in ele.loops: - loopno = loop.calc_normal() - if loopno.dot(no) < 0.0: - loopno.negate() - if angle_fn(loopno, 1000.0) > angle_distort: - return True - return False - bm = mesh_helpers.bmesh_copy_from_object(obj, transform=True, triangulate=False) bm.normal_update() - faces_distort = array.array('i', (i for i, ele in enumerate(bm.faces) if face_is_distorted(ele))) + faces_distort = array.array( + 'i', + (i for i, ele in enumerate(bm.faces) if mesh_helpers.face_is_distorted(ele, angle_distort)) + ) info.append(("Non-Flat Faces: %d" % len(faces_distort), (bmesh.types.BMFace, faces_distort))) @@ -418,20 +407,11 @@ class MESH_OT_Print3D_Clean_Distorted(Operator): print_3d = scene.print_3d angle_distort = print_3d.angle_distort - def face_is_distorted(ele): - no = ele.normal - angle_fn = no.angle - for loop in ele.loops: - if angle_fn(loop.calc_normal(), 1000.0) > angle_distort: - return True - return False - obj = context.active_object bm = mesh_helpers.bmesh_from_object(obj) bm.normal_update() - elems_triangulate = [ele for ele in bm.faces if face_is_distorted(ele)] + elems_triangulate = [ele for ele in bm.faces if mesh_helpers.face_is_distorted(ele, angle_distort)] - # edit if elems_triangulate: bmesh.ops.triangulate(bm, faces=elems_triangulate) mesh_helpers.bmesh_to_object(obj, bm) diff --git a/oscurart_tools/__init__.py b/oscurart_tools/__init__.py index 8affc8bb..d0f54c24 100644 --- a/oscurart_tools/__init__.py +++ b/oscurart_tools/__init__.py @@ -196,6 +196,8 @@ class OscPanelMesh(Panel): colrow = col.row(align=1) colrow.operator("mesh.reconst_osc", icon="UV_SYNC_SELECT") colrow = col.row(align=1) + colrow.operator("mesh.vertex_color_mask", icon="GROUP_VCOL") + colrow = col.row(align=1) colrow.operator("mesh.overlap_uv_faces", icon="UV_FACESEL") colrow = col.row(align=1) colrow.operator("mesh.uv_island_copy", icon="COPYDOWN") @@ -210,6 +212,7 @@ class OscPanelMesh(Panel): colrow = col.row(align=1) colrow.operator("mesh.create_edit_multimesh", icon="IMPORT", text= "StartEdit") colrow.operator("mesh.apply_edit_multimesh", icon="EXPORT", text="FinishEdit") + class OscPanelShapes(Panel): bl_idname = "Oscurart Shapes Tools" diff --git a/oscurart_tools/oscurart_meshes.py b/oscurart_tools/oscurart_meshes.py index ea899d53..3282c66a 100644 --- a/oscurart_tools/oscurart_meshes.py +++ b/oscurart_tools/oscurart_meshes.py @@ -19,6 +19,7 @@ # <pep8 compliant> import bpy +from mathutils import Vector from bpy.types import Operator from bpy.props import ( IntProperty, @@ -31,6 +32,7 @@ import bmesh import time import blf from bpy_extras.view3d_utils import location_3d_to_region_2d +from random import uniform C = bpy.context D = bpy.data @@ -152,7 +154,7 @@ class SelectMenor(Operator): # -------------------------RESYM VG---------------------------------- -class resymVertexGroups(Operator): +class rvg(Operator): bl_idname = "mesh.resym_vertex_weights_osc" bl_label = "Resym Vertex Weights" bl_description = ("Copies the symetrical weight value of the vertices on the X axys\n" @@ -580,12 +582,12 @@ def defCopyUvsIsland(self, context): bpy.ops.object.mode_set(mode="EDIT") -def defPasteUvsIsland(self, context): +def defPasteUvsIsland(self, uvOffset, rotateUv,context): bpy.ops.object.mode_set(mode="OBJECT") selPolys = [poly.index for poly in bpy.context.object.data.polygons if poly.select] - + for island in selPolys: - bpy.ops.object.mode_set(mode="EDIT") + bpy.ops.object.mode_set(mode="EDIT") bpy.ops.mesh.select_all(action="DESELECT") bpy.ops.object.mode_set(mode="OBJECT") bpy.context.object.data.polygons[island].select = True @@ -601,10 +603,20 @@ def defPasteUvsIsland(self, context): TobLoop.append(li) for source,target in zip(range(min(obLoop),max(obLoop)+1),range(min(TobLoop),max(TobLoop)+1)): - bpy.context.object.data.uv_layers.active.data[target].uv = bpy.context.object.data.uv_layers.active.data[source].uv - + bpy.context.object.data.uv_layers.active.data[target].uv = bpy.context.object.data.uv_layers.active.data[source].uv + Vector((uvOffset,0)) + bpy.ops.object.mode_set(mode="EDIT") + if rotateUv: + bpy.ops.object.mode_set(mode="OBJECT") + for poly in selPolys: + bpy.context.object.data.polygons[poly].select = True + bpy.ops.object.mode_set(mode="EDIT") + bm = bmesh.from_edit_mesh(bpy.context.object.data) + bmesh.ops.reverse_uvs(bm, faces=[f for f in bm.faces if f.select]) + bmesh.ops.rotate_uvs(bm, faces=[f for f in bm.faces if f.select]) + #bmesh.update_edit_mesh(bpy.context.object.data, tessface=False, destructive=False) + class CopyUvIsland(Operator): @@ -628,7 +640,16 @@ class PasteUvIsland(Operator): bl_idname = "mesh.uv_island_paste" bl_label = "Paste Uv Island" bl_options = {"REGISTER", "UNDO"} + + uvOffset = BoolProperty( + name="Uv Offset", + default=False + ) + rotateUv = BoolProperty( + name="Rotate Uv Corner", + default=False + ) @classmethod def poll(cls, context): return (context.active_object is not None and @@ -636,7 +657,7 @@ class PasteUvIsland(Operator): context.active_object.mode == "EDIT") def execute(self, context): - defPasteUvsIsland(self, context) + defPasteUvsIsland(self, self.uvOffset, self.rotateUv, context) return {'FINISHED'} @@ -694,4 +715,53 @@ class ApplyEditMultimesh(Operator): bpy.context.scene.objects.unlink(ob) return {'FINISHED'} -
\ No newline at end of file +# -------------------------VERTEX COLOR MASK---------------------------------- + + +class resymVertexGroups(Operator): + bl_idname = "mesh.vertex_color_mask" + bl_label = "Vertex Color Mask" + bl_description = ("Create a Vertex Color Mask") + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + obj = context.active_object + return obj is not None + + def execute(self, context): + obj = bpy.context.active_object + mesh= obj.data + + bpy.ops.object.mode_set(mode='EDIT', toggle=False) + bpy.ops.mesh.select_all(action="DESELECT") + + bm = bmesh.from_edit_mesh(mesh) + bm.faces.ensure_lookup_table() + + islands = [] + faces = bm.faces + + try: + color_layer = bm.loops.layers.color["RGBMask"] + except: + color_layer = bm.loops.layers.color.new("RGBMask") + + while faces: + faces[0].select_set(True) + bpy.ops.mesh.select_linked() + islands.append([f for f in faces if f.select]) + bpy.ops.mesh.hide(unselected=False) + faces = [f for f in bm.faces if not f.hide] + + bpy.ops.mesh.reveal() + + for island in islands: + color = (uniform(0,1),uniform(0,1),uniform(0,1),1) + for face in island: + for loop in face.loops: + loop[color_layer] = color + + bpy.ops.object.mode_set(mode="VERTEX_PAINT") + + return {'FINISHED'}
\ No newline at end of file diff --git a/oscurart_tools/oscurart_render.py b/oscurart_tools/oscurart_render.py index e65b06bf..a758fae1 100644 --- a/oscurart_tools/oscurart_render.py +++ b/oscurart_tools/oscurart_render.py @@ -70,12 +70,21 @@ def defRenderAll(frametype, scenes): for i in scene.render.layers: i.use = False layer.use = 1 + print("SCENE: %s" % scene.name) print("LAYER: %s" % layer.name) print("OVERRIDE: %s" % str(proptolist)) - scene.render.filepath = os.path.join( - os.path.dirname(renpath), filename, scene.name, layer.name, "%s_%s_%s" % - (filename, scene.name, layer.name)) + #scene.render.filepath = os.path.join( + # os.path.dirname(renpath), filename, scene.name, layer.name, "%s_%s_%s" % + # (filename, scene.name, layer.name)) + tokens = { + "$Scene":scene.name, + "$File":os.path.basename(bpy.data.filepath).split(".")[0], + "$Layer":layer.name, + "$Camera":scene.camera.name} + + scene.render.filepath = renpath.replace("$Scene",tokens["$Scene"]).replace("$File",tokens["$File"]).replace("$Layer",tokens["$Layer"]).replace("$Camera",tokens["$Camera"]) + bpy.context.window.screen.scene = scene bpy.ops.render.render( animation=True, @@ -209,7 +218,15 @@ def defoscBatchMaker(TYPE, BIN): SHFILE = os.path.join( bpy.data.filepath.rpartition(SYSBAR)[0], FILENAME + EXTSYS) - + + renpath = bpy.context.scene.render.filepath + tokens = { + "$Scene":bpy.context.scene.name, + "$File":os.path.basename(bpy.data.filepath).split(".")[0], + "$Layer":bpy.context.scene.render.layers.active.name, + "$Camera":bpy.context.scene.camera.name} + + rfp = bpy.context.scene.render.filepath.replace("$Scene",tokens["$Scene"]).replace("$File",tokens["$File"]).replace("$Layer",tokens["$Layer"]).replace("$Camera",tokens["$Camera"]) with open(SHFILE, "w") as FILE: # assign permission in linux if EXTSYS == ".sh": @@ -219,13 +236,13 @@ def defoscBatchMaker(TYPE, BIN): print( "** Oscurart Batch maker can not modify the permissions.") if not BIN: - FILE.writelines("%s%s%s -b %s -x 1 -o %s -P %s%s.py -s %s -e %s -a" % - (QUOTES, BINDIR, QUOTES, bpy.data.filepath, bpy.context.scene.render.filepath, + FILE.writelines("%s%s%s -b %s -x 1 -P %s%s.py -s %s -e %s " % + (QUOTES, BINDIR, QUOTES, bpy.data.filepath, bpy.data.filepath.rpartition(SYSBAR)[0] + SYSBAR, TYPE, str(bpy.context.scene.frame_start), str(bpy.context.scene.frame_end))) else: - FILE.writelines("%s -b %s -x 1 -o %s -P %s%s.py -s %s -e %s -a" % - ("blender", bpy.data.filepath, bpy.context.scene.render.filepath, + FILE.writelines("%s -b %s -x 1 -P %s%s.py -s %s -e %s " % + ("blender", bpy.data.filepath, bpy.data.filepath.rpartition(SYSBAR)[0] + SYSBAR, TYPE, str(bpy.context.scene.frame_start), str(bpy.context.scene.frame_end))) diff --git a/paint_palette.py b/paint_palette.py index 7597fde1..577b292b 100644 --- a/paint_palette.py +++ b/paint_palette.py @@ -22,7 +22,7 @@ bl_info = { "name": "Paint Palettes", "author": "Dany Lebel (Axon D)", - "version": (0, 9, 3), + "version": (0, 9, 4), "blender": (2, 63, 0), "location": "Image Editor and 3D View > Any Paint mode > Color Palette or Weight Palette panel", "description": "Palettes for color and weight paint modes", @@ -44,20 +44,20 @@ with the brush by using the button under the color. import bpy from bpy.types import ( - Operator, - Menu, - Panel, - PropertyGroup, - ) + Operator, + Menu, + Panel, + PropertyGroup, +) from bpy.props import ( - BoolProperty, - FloatProperty, - FloatVectorProperty, - IntProperty, - StringProperty, - PointerProperty, - CollectionProperty, - ) + BoolProperty, + FloatProperty, + FloatVectorProperty, + IntProperty, + StringProperty, + PointerProperty, + CollectionProperty, +) def update_panels(): @@ -97,6 +97,14 @@ def update_weight_value(): return None +def check_path_return(): + from os.path import normpath + preset_path = bpy.path.abspath(bpy.context.scene.palette_props.presets_folder) + paths = normpath(preset_path) + + return paths if paths else "" + + class PALETTE_MT_menu(Menu): bl_label = "Presets" preset_subdir = "" @@ -109,61 +117,62 @@ class PALETTE_MT_menu(Menu): import bpy.utils layout = self.layout + + if bpy.data.filepath == "": + layout.label("*Please save the .blend file first*") + return + if not searchpaths[0]: layout.label("* Missing Paths *") + return # collect paths - else: - files = [] - for directory in searchpaths: - files.extend([(f, os.path.join(directory, f)) for f in os.listdir(directory)]) + files = [] + for directory in searchpaths: + files.extend([(f, os.path.join(directory, f)) for f in os.listdir(directory)]) - files.sort() + files.sort() - for f, filepath in files: + for f, filepath in files: - if f.startswith("."): - continue - # do not load everything from the given folder, only .gpl files - if f[-4:] != ".gpl": - continue + if f.startswith("."): + continue + # do not load everything from the given folder, only .gpl files + if f[-4:] != ".gpl": + continue - preset_name = bpy.path.display_name(f) - props = layout.operator(operator, text=preset_name) + preset_name = bpy.path.display_name(f) + props = layout.operator(operator, text=preset_name) - for attr, value in props_default.items(): - setattr(props, attr, value) + for attr, value in props_default.items(): + setattr(props, attr, value) - props.filepath = filepath - if operator == "palette.load_gimp_palette": - props.menu_idname = self.bl_idname + props.filepath = filepath + if operator == "palette.load_gimp_palette": + props.menu_idname = self.bl_idname def draw_preset(self, context): - """Define these on the subclass - - preset_operator - - preset_subdir - """ - import bpy - self.path_menu([bpy.context.scene.palette_props.presets_folder], self.preset_operator) + paths = check_path_return() + self.path_menu([paths], self.preset_operator) draw = draw_preset -class LoadGimpPalette(Operator): +class PALETTE_OT_load_gimp_palette(Operator): """Execute a preset""" bl_idname = "palette.load_gimp_palette" bl_label = "Load a Gimp palette" filepath = StringProperty( - name="Path", - description="Path of the .gpl file to load", - default="" - ) + name="Path", + description="Path of the .gpl file to load", + default="" + ) menu_idname = StringProperty( - name="Menu ID Name", - description="ID name of the menu this was called from", - default="" - ) + name="Menu ID Name", + description="ID name of the menu this was called from", + default="" + ) def execute(self, context): from os.path import basename @@ -255,14 +264,16 @@ class WriteGimpPalette(): bl_options = {'REGISTER'} # only because invoke_props_popup requires name = StringProperty( - name="Name", - description="Name of the preset, used to make the path name", - maxlen=64, default="" - ) + name="Name", + description="Name of the preset, used to make the path name", + maxlen=64, + options={'SKIP_SAVE'}, + default="" + ) remove_active = BoolProperty( - default=False, - options={'HIDDEN'} - ) + default=False, + options={'HIDDEN'} + ) @staticmethod def as_filename(name): # could reuse for other presets @@ -278,26 +289,25 @@ class WriteGimpPalette(): self.pre_cb(context) preset_menu_class = getattr(bpy.types, self.preset_menu) + target_path = check_path_return() - if not self.remove_active: + if not target_path: + self.report({'WARNING'}, "Failed to create presets path") + return {'CANCELLED'} + + if not os.path.exists(target_path): + self.report({'WARNING'}, + "Failure to open the saved Palettes Folder. Check if the path exists") + return {'CANCELLED'} + if not self.remove_active: if not self.name: + self.report({'INFO'}, + "No name is given for the preset entry. Operation Cancelled") return {'FINISHED'} filename = self.as_filename(self.name) - target_path = pp.presets_folder - - if not target_path: - self.report({'WARNING'}, "Failed to create presets path") - return {'CANCELLED'} - - if not os.path.exists(target_path): - self.report({'WARNING'}, - "Failure to open the saved Palletes Folder. Check if the path exists") - return {'CANCELLED'} - filepath = os.path.join(target_path, filename) + ".gpl" - file_preset = open(filepath, 'wb') gpl = "GIMP Palette\n" gpl += "Name: %s\n" % filename @@ -312,18 +322,19 @@ class WriteGimpPalette(): file_preset.close() pp.palette_name = filename + preset_menu_class.bl_label = bpy.path.display_name(filename) + + self.report({'INFO'}, "Created Palette: {}".format(filepath)) else: preset_active = preset_menu_class.bl_label + filename = self.as_filename(preset_active) - # fairly sloppy but convenient. - filepath = bpy.utils.preset_find(preset_active, self.preset_subdir) - - if not filepath: - filepath = bpy.utils.preset_find(preset_active, - self.preset_subdir, display_name=True) + filepath = os.path.join(target_path, filename) + ".gpl" - if not filepath: + if not filepath or not os.path.exists(filepath): + self.report({'WARNING'}, "Preset could not be found. Operation Cancelled") + self.reset_preset_name(preset_menu_class, pp) return {'CANCELLED'} if hasattr(self, "remove"): @@ -331,18 +342,24 @@ class WriteGimpPalette(): else: try: os.remove(filepath) + self.report({'INFO'}, "Deleted palette: {}".format(filepath)) except: import traceback traceback.print_exc() - # XXX, stupid! - preset_menu_class.bl_label = "Presets" + self.reset_preset_name(preset_menu_class, pp) if hasattr(self, "post_cb"): self.post_cb(context) return {'FINISHED'} + @staticmethod + def reset_preset_name(presets, props): + # XXX, still stupid! + presets.bl_label = "Presets" + props.palette_name = "" + def check(self, context): self.name = self.as_filename(self.name) @@ -350,11 +367,11 @@ class WriteGimpPalette(): if not self.remove_active: wm = context.window_manager return wm.invoke_props_dialog(self) - else: - return self.execute(context) + return self.execute(context) -class AddPresetPalette(WriteGimpPalette, Operator): + +class PALETTE_OT_preset_add(WriteGimpPalette, Operator): bl_idname = "palette.preset_add" bl_label = "Add Palette Preset" preset_menu = "PALETTE_MT_menu" @@ -439,14 +456,13 @@ class IMAGE_OT_select_color(Operator): def color_palette_draw(self, context): - palette_props = bpy.context.scene.palette_props + palette_props = context.scene.palette_props layout = self.layout - bpy.types.PALETTE_MT_menu.preset_subdir = palette_props.presets_folder row = layout.row(align=True) - row.menu("PALETTE_MT_menu", text=palette_props.palette_name.rstrip()) - row.operator("palette.preset_add", text="", icon="ZOOMIN") + row.menu("PALETTE_MT_menu", text=PALETTE_MT_menu.bl_label) + row.operator("palette.preset_add", text="", icon="ZOOMIN").remove_active = False row.operator("palette.preset_add", text="", icon="ZOOMOUT").remove_active = True col = layout.column(align=True) @@ -487,8 +503,6 @@ def color_palette_draw(self, context): row = layout.row() row.prop(palette_props, "presets_folder", text="") - pass - class BrushButtonsPanel(): bl_space_type = 'IMAGE_EDITOR' @@ -612,11 +626,12 @@ class VIEW3D_OT_reset_weight_palette(Operator): def execute(self, context): try: palette_props = context.scene.palette_props - dict_defs = {0: 0.0, 1: 0.1, 2: 0.25, - 3: 0.333, 4: 0.4, 5: 0.5, - 6: 0.6, 7: 0.6666, 8: 0.75, - 9: 0.9, 10: 1.0 - } + dict_defs = { + 0: 0.0, 1: 0.1, 2: 0.25, + 3: 0.333, 4: 0.4, 5: 0.5, + 6: 0.6, 7: 0.6666, 8: 0.75, + 9: 0.9, 10: 1.0 + } current_idx = palette_props.current_weight_index palette_props.weight = dict_defs[current_idx] @@ -673,30 +688,20 @@ class VIEW3D_PT_weight_palette(PaintPanel, Panel): row.operator("paint.reset_weight_palette", text="Reset") -class Colors(PropertyGroup): +class PALETTE_Colors(PropertyGroup): """Class for colors CollectionProperty""" color = FloatVectorProperty( - name="", - description="", - default=(0.8, 0.8, 0.8), - min=0, max=1, - step=1, precision=3, - subtype='COLOR_GAMMA', - size=3 - ) - - -class Weights(PropertyGroup): - """Class for Weights Collection Property""" - weight = FloatProperty( - default=0.0, - min=0.0, - max=1.0, - precision=3 - ) + name="", + description="", + default=(0.8, 0.8, 0.8), + min=0, max=1, + step=1, precision=3, + subtype='COLOR_GAMMA', + size=3 + ) -class PaletteProps(PropertyGroup): +class PALETTE_Props(PropertyGroup): def update_color_name(self, context): pp = bpy.context.scene.palette_props @@ -742,133 +747,151 @@ class PaletteProps(PropertyGroup): return None palette_name = StringProperty( - name="Palette Name", - default="Preset", - subtype='FILE_NAME' - ) + name="Palette Name", + default="Preset", + subtype='FILE_NAME' + ) color_name = StringProperty( - name="", - description="Color Name", - default="Untitled", - update=update_color_name - ) + name="", + description="Color Name", + default="Untitled", + update=update_color_name + ) columns = IntProperty( - name="Columns", - description="Number of Columns", - min=0, max=16, - default=0 - ) + name="Columns", + description="Number of Columns", + min=0, max=16, + default=0 + ) index = IntProperty( - name="Index", - description="Move Selected Color", - min=0, - update=move_color - ) + name="Index", + description="Move Selected Color", + min=0, + update=move_color + ) notes = StringProperty( - name="Palette Notes", - default="#\n" - ) + name="Palette Notes", + default="#\n" + ) current_color_index = IntProperty( - name="Current Color Index", - description="", - default=0, - min=0 - ) + name="Current Color Index", + description="", + default=0, + min=0 + ) current_weight_index = IntProperty( - name="Current Color Index", - description="", - default=10, - min=-1 - ) + name="Current Color Index", + description="", + default=10, + min=-1 + ) presets_folder = StringProperty(name="", - description="Palettes Folder", - subtype="DIR_PATH" - ) + description="Palettes Folder", + subtype="DIR_PATH", + default="//" + ) colors = CollectionProperty( - type=Colors - ) + type=PALETTE_Colors + ) weight = FloatProperty( - name="Weight", - description="Modify the active Weight preset slot value", - default=0.0, - min=0.0, max=1.0, - precision=3, - update=update_weight - ) + name="Weight", + description="Modify the active Weight preset slot value", + default=0.0, + min=0.0, max=1.0, + precision=3, + update=update_weight + ) weight_0 = FloatProperty( - default=0.0, - min=0.0, max=1.0, - precision=3 - ) + default=0.0, + min=0.0, max=1.0, + precision=3 + ) weight_1 = FloatProperty( - default=0.1, - min=0.0, max=1.0, - precision=3 - ) + default=0.1, + min=0.0, max=1.0, + precision=3 + ) weight_2 = FloatProperty( - default=0.25, - min=0.0, max=1.0, - precision=3 - ) + default=0.25, + min=0.0, max=1.0, + precision=3 + ) weight_3 = FloatProperty( - default=0.333, - min=0.0, max=1.0, - precision=3 - ) + default=0.333, + min=0.0, max=1.0, + precision=3 + ) weight_4 = FloatProperty( - default=0.4, - min=0.0, max=1.0, - precision=3 - ) + default=0.4, + min=0.0, max=1.0, + precision=3 + ) weight_5 = FloatProperty( - default=0.5, - min=0.0, max=1.0, - precision=3 - ) + default=0.5, + min=0.0, max=1.0, + precision=3 + ) weight_6 = FloatProperty( - default=0.6, - min=0.0, max=1.0, - precision=3 - ) + default=0.6, + min=0.0, max=1.0, + precision=3 + ) weight_7 = FloatProperty( - default=0.6666, - min=0.0, max=1.0, - precision=3 - ) + default=0.6666, + min=0.0, max=1.0, + precision=3 + ) weight_8 = FloatProperty( - default=0.75, - min=0.0, max=1.0, - precision=3 - ) + default=0.75, + min=0.0, max=1.0, + precision=3 + ) weight_9 = FloatProperty( - default=0.9, - min=0.0, max=1.0, - precision=3 - ) + default=0.9, + min=0.0, max=1.0, + precision=3 + ) weight_10 = FloatProperty( - default=1.0, - min=0.0, max=1.0, - precision=3 - ) - pass + default=1.0, + min=0.0, max=1.0, + precision=3 + ) + + +classes = ( + PALETTE_MT_menu, + PALETTE_OT_load_gimp_palette, + PALETTE_OT_preset_add, + PALETTE_OT_add_color, + PALETTE_OT_remove_color, + PALETTE_OT_sample_tool_color, + IMAGE_OT_select_color, + IMAGE_PT_color_palette, + VIEW3D_PT_color_palette, + VIEW3D_OT_select_weight, + VIEW3D_OT_reset_weight_palette, + VIEW3D_PT_weight_palette, + PALETTE_Colors, + PALETTE_Props, +) def register(): - bpy.utils.register_module(__name__) + for cls in classes: + bpy.utils.register_class(cls) bpy.types.Scene.palette_props = PointerProperty( - type=PaletteProps, - name="Palette Props", - description="" - ) - pass + type=PALETTE_Props, + name="Palette Props", + description="" + ) def unregister(): - bpy.utils.unregister_module(__name__) + for cls in reversed(classes): + bpy.utils.unregister_class(cls) del bpy.types.Scene.palette_props - pass if __name__ == "__main__": diff --git a/presets/pov/lamp/01_(5400K)_Direct_Sun.py b/presets/pov/lamp/01_(5400K)_Direct_Sun.py new file mode 100644 index 00000000..7f53ce06 --- /dev/null +++ b/presets/pov/lamp/01_(5400K)_Direct_Sun.py @@ -0,0 +1,10 @@ +#Since the dawn of time + +import bpy +bpy.context.object.data.type = 'SUN' +lampdata = bpy.context.object.data + +lampdata.color = (1.0, 1.0, 0.9843137264251709) +lampdata.energy = 1.2 #100 000lux +#lampdata.distance = 0.001 +#lampdata.falloff_type = 'INVERSE_SQUARE'
\ No newline at end of file diff --git a/presets/pov/lamp/02_(5400K)_High_Noon_Sun.py b/presets/pov/lamp/02_(5400K)_High_Noon_Sun.py new file mode 100644 index 00000000..6d3083a6 --- /dev/null +++ b/presets/pov/lamp/02_(5400K)_High_Noon_Sun.py @@ -0,0 +1,17 @@ +#Since the dawn of time + +import bpy +bpy.context.object.rotation_euler = (0,0,0)#And loc HIGH +bpy.context.object.location = (0,0,700000000) +bpy.context.object.data.type = 'AREA' +lampdata = bpy.context.object.data + +lampdata.shape = 'SQUARE' +lampdata.size = 30000000#0.02 +#lampdata.size_y = 0.02 +lampdata.shadow_ray_samples_x = 2 +#lampdata.shadow_ray_samples_y = 3 +lampdata.color = (1.0, 1.0, 1.0) +lampdata.energy = 1.094316#91193 #lux +lampdata.distance =695699968 + diff --git a/presets/pov/lamp/03_(6000K)_Daylight_Window.py b/presets/pov/lamp/03_(6000K)_Daylight_Window.py new file mode 100644 index 00000000..a9781f57 --- /dev/null +++ b/presets/pov/lamp/03_(6000K)_Daylight_Window.py @@ -0,0 +1,13 @@ +#Since the dawn of time + +import bpy +bpy.context.object.data.type = 'AREA' +lampdata = bpy.context.object.data + +lampdata.size = 1.2 +lampdata.size_y = 2.10 +lampdata.shadow_ray_samples_x = 2 +lampdata.shadow_ray_samples_y = 3 +lampdata.color = (1.0, 1.0, 1.0) +lampdata.energy = 1.094316#91193 #lux +lampdata.distance = 1.0 diff --git a/presets/pov/lamp/04_(6000K)_2500W_HMI_(Halogen_Metal_Iodide).py b/presets/pov/lamp/04_(6000K)_2500W_HMI_(Halogen_Metal_Iodide).py new file mode 100644 index 00000000..0bfa95d8 --- /dev/null +++ b/presets/pov/lamp/04_(6000K)_2500W_HMI_(Halogen_Metal_Iodide).py @@ -0,0 +1,14 @@ +#After 1969 +#made specifically for film and entertainment applications + +import bpy +bpy.context.object.data.type = 'SPOT' +lampdata = bpy.context.object.data + +lampdata.show_cone = True +lampdata.spot_size = 0.872665 +lampdata.spot_blend = 0.9 +lampdata.color = (0.99, 0.9882352948188782, 0.998) +lampdata.energy = 223.81796 #240000lm/21.446(=lux)*0.004*2.5(distance) *2 for distance is the point of half strength +lampdata.distance = 0.001 +lampdata.falloff_type = 'INVERSE_SQUARE'
\ No newline at end of file diff --git a/presets/pov/lamp/05_(4000K)_100W_Metal_Halide.py b/presets/pov/lamp/05_(4000K)_100W_Metal_Halide.py new file mode 100644 index 00000000..e91d3f9a --- /dev/null +++ b/presets/pov/lamp/05_(4000K)_100W_Metal_Halide.py @@ -0,0 +1,14 @@ +#After 1962 +#Common uses: outdoor lighting where good color rendering is needed, television/film lighting, sports fields, car headlights, flood lights, heavy flashlights, green house applications + +import bpy +bpy.context.object.data.type = 'SPOT' +lampdata = bpy.context.object.data + +lampdata.show_cone = True +lampdata.spot_size = 0.6 +lampdata.spot_blend = 0.9 +lampdata.color = (0.9490196108818054, 0.9882352948188782, 1.0) +lampdata.energy = 20.98293#9000lm/21.446(=lux)*0.004*6.25(distance) *2 for distance is the point of half strength +lampdata.distance = 0.025 +lampdata.falloff_type = 'INVERSE_SQUARE'
\ No newline at end of file diff --git a/presets/pov/lamp/06_(3200K)_100W_Quartz_Halogen.py b/presets/pov/lamp/06_(3200K)_100W_Quartz_Halogen.py new file mode 100644 index 00000000..b58edf17 --- /dev/null +++ b/presets/pov/lamp/06_(3200K)_100W_Quartz_Halogen.py @@ -0,0 +1,16 @@ +#since 1960, no longer manufactured since 2016 +#8mm projectors +#used in many automobiles headlamps ; outdoor lighting systems ; watercraft ; desktop lamps (smaller power). +#theatrical and studio (film and television) fixtures, including Ellipsoidal reflector spotlights, Source Four, and Fresnels; PAR Cans + +import bpy +bpy.context.object.data.type = 'SPOT' +lampdata = bpy.context.object.data + +lampdata.show_cone = True +lampdata.spot_size = 1.9 +lampdata.spot_blend = 0.9 +lampdata.color = (1.0, 0.9450980424880981, 0.8784313797950745) +lampdata.energy = 12.43433#5000/21.446 #lumen values/20 or lux when available used as a basis +lampdata.distance = 0.015#energy calculated for length 0.075 but width gives better result +lampdata.falloff_type = 'INVERSE_SQUARE' diff --git a/presets/pov/lamp/07_(2850K)_100w_Tungsten.py b/presets/pov/lamp/07_(2850K)_100w_Tungsten.py new file mode 100644 index 00000000..72675de3 --- /dev/null +++ b/presets/pov/lamp/07_(2850K)_100w_Tungsten.py @@ -0,0 +1,10 @@ +#1908 - today + +import bpy +bpy.context.object.data.type = 'POINT' +lampdata = bpy.context.object.data + +lampdata.color = (1.0, 0.8392156958580017, 0.6666666865348816) +lampdata.energy = 7.46060#3.7303#1000/21.446/(lampdistance/candledistance) #lumen values/21.446 or lux when available used as a basis +lampdata.distance = 0.05 +lampdata.falloff_type = 'INVERSE_SQUARE'
\ No newline at end of file diff --git a/presets/pov/lamp/08_(2600K)_40w_Tungsten.py b/presets/pov/lamp/08_(2600K)_40w_Tungsten.py new file mode 100644 index 00000000..88ef46a0 --- /dev/null +++ b/presets/pov/lamp/08_(2600K)_40w_Tungsten.py @@ -0,0 +1,10 @@ +#since 1908 + +import bpy +bpy.context.object.data.type = 'POINT' +lampdata = bpy.context.object.data + +lampdata.color = (1.0, 0.8196078431372549, 0.6980392156862745) +lampdata.energy = 2.98424#400/21.446 #lumen values/21.446 or lux when available used as a basis +lampdata.distance = 0.05 +lampdata.falloff_type = 'INVERSE_SQUARE'
\ No newline at end of file diff --git a/presets/pov/lamp/09_(5000K)_75W_Full_Spectrum_Fluorescent_T12.py b/presets/pov/lamp/09_(5000K)_75W_Full_Spectrum_Fluorescent_T12.py new file mode 100644 index 00000000..b0a23eda --- /dev/null +++ b/presets/pov/lamp/09_(5000K)_75W_Full_Spectrum_Fluorescent_T12.py @@ -0,0 +1,14 @@ +#Available since 1979 (Triphosphor lamps) + +import bpy +bpy.context.object.data.type = 'AREA' +lampdata = bpy.context.object.data + +lampdata.size = 0.038 +lampdata.size_y = 2.40284 +lampdata.shadow_ray_samples_x = 1 +lampdata.shadow_ray_samples_y = 2 +lampdata.color = (1.0, 0.95686274766922, 0.9490200281143188) +lampdata.energy = 4.45304#4775lm/21.446(=lux)*0.004(distance) *2 for distance is the point of half strength 6200lm? +lampdata.distance = 1.0 #dist values multiplied by 10 for area lights for same power as bulb/spot/... +#lampdata.falloff_type = 'INVERSE_SQUARE'
\ No newline at end of file diff --git a/presets/pov/lamp/10_(4300K)_40W_Vintage_Fluorescent_T12.py b/presets/pov/lamp/10_(4300K)_40W_Vintage_Fluorescent_T12.py new file mode 100644 index 00000000..d03ce6fd --- /dev/null +++ b/presets/pov/lamp/10_(4300K)_40W_Vintage_Fluorescent_T12.py @@ -0,0 +1,14 @@ +#since 1939 , T12 no longer manufactured since T8 propagated + +import bpy +bpy.context.object.data.type = 'AREA' +lampdata = bpy.context.object.data + +lampdata.size = 0.038 +lampdata.size_y = 1.2192 +lampdata.shadow_ray_samples_x = 1 +lampdata.shadow_ray_samples_y = 2 +lampdata.color = (0.901, 1.0, 0.979) +lampdata.energy = 2.14492#2300lm/21.446(=lux)*0.004*2.5(distance) *2 for distance is the point of half strength +lampdata.distance = 1.0 #dist values multiplied by 10 for area lights for same power as bulb/spot/... +#lampdata.falloff_type = 'INVERSE_SQUARE'
\ No newline at end of file diff --git a/presets/pov/lamp/11_(5000K)_18W_Standard_Fluorescent_T8.py b/presets/pov/lamp/11_(5000K)_18W_Standard_Fluorescent_T8.py new file mode 100644 index 00000000..6fef1763 --- /dev/null +++ b/presets/pov/lamp/11_(5000K)_18W_Standard_Fluorescent_T8.py @@ -0,0 +1,13 @@ +#since 1973 + +import bpy +bpy.context.object.data.type = 'AREA' +lampdata = bpy.context.object.data + +lampdata.size = 0.026 +lampdata.size_y = 0.59 +lampdata.shadow_ray_samples_x = 1 +lampdata.shadow_ray_samples_y = 2 +lampdata.color = (0.95686274766922, 1.0, 0.9803921580314636) +lampdata.energy = 1.25898#1350lm/21.446(=lux)*0.004*2.5(distance) *2 for distance is the point of half strength +lampdata.distance = 1.0 #dist values multiplied by 10 for area lights for same power as bulb/spot/... diff --git a/presets/pov/lamp/12_(4200K)_18W_Cool_White_Fluorescent_T8.py b/presets/pov/lamp/12_(4200K)_18W_Cool_White_Fluorescent_T8.py new file mode 100644 index 00000000..83f8dd4e --- /dev/null +++ b/presets/pov/lamp/12_(4200K)_18W_Cool_White_Fluorescent_T8.py @@ -0,0 +1,15 @@ +#Available since 1979 +#more common than the warm white + +import bpy +bpy.context.object.data.type = 'AREA' +lampdata = bpy.context.object.data + +lampdata.size = 0.026 +lampdata.size_y = 0.59 +lampdata.shadow_ray_samples_x = 1 +lampdata.shadow_ray_samples_y = 2 +lampdata.color = (0.8313725590705872, 0.9215686321258545, 1.0) +lampdata.energy = 1.25898#1350lm/21.446(=lux)*0.004*2.5(distance) *2 for distance is the point of half strength +lampdata.distance = 1.0 #dist values multiplied by 10 for area lights for same power as bulb/spot/... +#lampdata.falloff_type = 'INVERSE_SQUARE'
\ No newline at end of file diff --git a/presets/pov/lamp/13_(3000K)_18W_Warm_Fluorescent_T8.py b/presets/pov/lamp/13_(3000K)_18W_Warm_Fluorescent_T8.py new file mode 100644 index 00000000..e1cee557 --- /dev/null +++ b/presets/pov/lamp/13_(3000K)_18W_Warm_Fluorescent_T8.py @@ -0,0 +1,14 @@ +#Available since 1979 +#developed to get closer to tungsten atmospher in interiors + +import bpy +bpy.context.object.data.type = 'AREA' +lampdata = bpy.context.object.data + +lampdata.size = 0.026 +lampdata.size_y = 0.59 +lampdata.shadow_ray_samples_x = 1 +lampdata.shadow_ray_samples_y = 2 +lampdata.color = (1.0, 0.95686274766922, 0.8980392217636108) +lampdata.energy = 1.25898#1350lm/21.446(=lux)*0.004*2.5(distance) *2 for distance is the point of half strength +lampdata.distance = 1.0 #dist values multiplied by 10 for area lights for same power as bulb/spot/... diff --git a/presets/pov/lamp/14_(6500K)_54W_Grow_Light_Fluorescent_T5-HO.py b/presets/pov/lamp/14_(6500K)_54W_Grow_Light_Fluorescent_T5-HO.py new file mode 100644 index 00000000..55f84ab8 --- /dev/null +++ b/presets/pov/lamp/14_(6500K)_54W_Grow_Light_Fluorescent_T5-HO.py @@ -0,0 +1,13 @@ +#Available since 1995 + +import bpy +bpy.context.object.data.type = 'AREA' +lampdata = bpy.context.object.data + +lampdata.size = 0.016 +lampdata.size_y = 1.149 +lampdata.shadow_ray_samples_x = 1 +lampdata.shadow_ray_samples_y = 2 +lampdata.color = (1.0, 0.83, 0.986274528503418) +lampdata.energy = 4.66287 #0.93257#4.66287#5000lm/21.446(=lux)*0.004*2.5(distance) *2 for distance is the point of half strength +lampdata.distance = 0.1 #dist values multiplied by 10 for area lights for same power as bulb/spot/... diff --git a/presets/pov/lamp/15_(3200K)_40W_Induction_ Fluorescent.py b/presets/pov/lamp/15_(3200K)_40W_Induction_ Fluorescent.py new file mode 100644 index 00000000..1d0851e5 --- /dev/null +++ b/presets/pov/lamp/15_(3200K)_40W_Induction_ Fluorescent.py @@ -0,0 +1,14 @@ +#since the 1990's, +#Often circular or rectangular closed loop electrodeless fluorescent lamps + +import bpy +bpy.context.object.data.type = 'SPOT' +lampdata = bpy.context.object.data + +#lampdata.use_halo = True +lampdata.spot_size = 3.14 +lampdata.spot_blend = 0.9 +lampdata.color = (1.0, 0.9450980424880981, 0.8784313797950745) +lampdata.energy = 2.61121#2800/21.446 #lumen values/20 or lux when available used as a basis +lampdata.distance = 0.15#energy calculated for length 0.075 but width gives better result +lampdata.falloff_type = 'INVERSE_SQUARE' diff --git a/presets/pov/lamp/16_(2100K)_150W_High_Pressure_Sodium.py b/presets/pov/lamp/16_(2100K)_150W_High_Pressure_Sodium.py new file mode 100644 index 00000000..9ddb32cb --- /dev/null +++ b/presets/pov/lamp/16_(2100K)_150W_High_Pressure_Sodium.py @@ -0,0 +1,13 @@ +#Starting from 1964 + +import bpy +bpy.context.object.data.type = 'SPOT' +lampdata = bpy.context.object.data + +lampdata.show_cone = True +lampdata.color = (1.0, 0.772549033164978, 0.5607843399047852) +lampdata.energy = 4.47636#12000lm/21.446(=lux)*0.004(distance) *2 for distance is the point of half strength +lampdata.distance = 1.0 +lampdata.spot_size = 1.9 +lampdata.spot_blend = 0.9 +lampdata.falloff_type = 'INVERSE_SQUARE' diff --git a/presets/pov/lamp/17_(1700K)_135W_Low_Pressure_Sodium.py b/presets/pov/lamp/17_(1700K)_135W_Low_Pressure_Sodium.py new file mode 100644 index 00000000..d5e42678 --- /dev/null +++ b/presets/pov/lamp/17_(1700K)_135W_Low_Pressure_Sodium.py @@ -0,0 +1,11 @@ +#(1700K) 135W Low Pressure Sodium Vapor Starting from 1932 +#Mostly used for Outdoor city lighting, security lighting, long tunnel lighting + +import bpy +bpy.context.object.data.type = 'POINT' +lampdata = bpy.context.object.data + +lampdata.color = (1.0, 0.5764706134796143, 0.16078431904315948) +lampdata.energy = 8.43048#22600lm/21.446(=lux)*0.004(distance) *2 for distance is the point of half strength +lampdata.distance = 1.0 +lampdata.falloff_type = 'INVERSE_SQUARE'
\ No newline at end of file diff --git a/presets/pov/lamp/18_(6800K)_175W_Mercury_Vapor.py b/presets/pov/lamp/18_(6800K)_175W_Mercury_Vapor.py new file mode 100644 index 00000000..e1a5143c --- /dev/null +++ b/presets/pov/lamp/18_(6800K)_175W_Mercury_Vapor.py @@ -0,0 +1,13 @@ +#Starting from 1901 + +import bpy +bpy.context.object.data.type = 'SPOT' +lampdata = bpy.context.object.data + +lampdata.show_cone = True +lampdata.spot_size = 1.25 +lampdata.spot_blend = 0.9 +lampdata.color = (0.8470588326454163, 0.9686274528503418, 1.0) +lampdata.energy = 17.25263#7400lm/21.446(=lux)*0.004*6.25(distance) *2 for distance is the point of half strength +lampdata.distance = 0.025 +lampdata.falloff_type = 'INVERSE_SQUARE' diff --git a/presets/pov/lamp/19_(5200K)_700W_Carbon_Arc.py b/presets/pov/lamp/19_(5200K)_700W_Carbon_Arc.py new file mode 100644 index 00000000..139dc8bb --- /dev/null +++ b/presets/pov/lamp/19_(5200K)_700W_Carbon_Arc.py @@ -0,0 +1,18 @@ +#Starting from 1876 (first type of commercial lamps developed with electricity) +#Carbon arc lamps were being phased out after the 1910s. +#For general lighting the lamp was replaced by the 1920s and 30s in most cities. +#The lamp continued to be used for spot lights, film production lighting and film projector lamps. +#Most of the remaining carbon arc lamps ceased production by the 1980s + +import bpy +bpy.context.object.data.type = 'SPOT' +lampdata = bpy.context.object.data + +#lampdata.use_halo = True +lampdata.show_cone = True +lampdata.spot_size = 1.5 +lampdata.spot_blend = 0.3 +lampdata.color = (1.0, 0.9803921580314636, 0.95686274766922) +lampdata.energy = 51.29162#55000lm/21.446(=lux)*0.004*2.5(distance) *2 for distance is the point of half strength +lampdata.distance = 0.01 +lampdata.falloff_type = 'INVERSE_SQUARE'
\ No newline at end of file diff --git a/presets/pov/lamp/20_(6500K)_15W_LED_Spot.py b/presets/pov/lamp/20_(6500K)_15W_LED_Spot.py new file mode 100644 index 00000000..1b70b9f1 --- /dev/null +++ b/presets/pov/lamp/20_(6500K)_15W_LED_Spot.py @@ -0,0 +1,12 @@ +#since 2008 + +import bpy +bpy.context.object.data.type = 'SPOT' +lampdata = bpy.context.object.data + +lampdata.show_cone = True +lampdata.spot_size = 1.39626 #80 degrees in radian +lampdata.spot_blend = 0.5 +lampdata.color = (1.0, 0.9372549057006836, 0.9686274528503418) +lampdata.energy = 1.39886#1500lm/21.446(=lux)*0.004*2.5(distance) *2 for distance is the point of half strength +lampdata.distance = 1.18 #dist values multiplied by 10 for area lights for same power as bulb/spot/... diff --git a/presets/pov/lamp/21_(2700K)_7W_OLED_Panel.py b/presets/pov/lamp/21_(2700K)_7W_OLED_Panel.py new file mode 100644 index 00000000..8f2ebb8f --- /dev/null +++ b/presets/pov/lamp/21_(2700K)_7W_OLED_Panel.py @@ -0,0 +1,14 @@ +#since 2025 +#inspired by OSRAM Early Future / IKEA Vitsand / OTI Lumionics Aerelight + +import bpy +bpy.context.object.data.type = 'AREA' +lampdata = bpy.context.object.data + +lampdata.size = 0.033 +lampdata.size_y = 0.133 +lampdata.shadow_ray_samples_x = 2 +lampdata.shadow_ray_samples_y = 2 +lampdata.color = (1.0, 0.8292156958580017, 0.6966666865348816) +lampdata.energy = 0.83932#900lm/21.446(=lux)*0.004*2.5(distance) *2 for distance is the point of half strength +lampdata.distance = 1.18 #dist values multiplied by 10 for area lights for same power as bulb/spot/... diff --git a/presets/pov/lamp/22_(30000K)_40W_Black_Light_Fluorescent.py b/presets/pov/lamp/22_(30000K)_40W_Black_Light_Fluorescent.py new file mode 100644 index 00000000..ef6b4725 --- /dev/null +++ b/presets/pov/lamp/22_(30000K)_40W_Black_Light_Fluorescent.py @@ -0,0 +1,12 @@ +#Starting from 1939 (World War II Byler's tubes) + +import bpy +bpy.context.object.data.type = 'AREA' +lampdata = bpy.context.object.data + +lampdata.size = 0.038 +lampdata.size_y = 1.2192 +lampdata.color = (0.6549019813537598, 0.0, 1.0) +lampdata.energy = 1.86515#100/21.446 #lumen values/21.446 or lux when available used as a basis +lampdata.distance = 0.4 #dist values multiplied by 10 for area lights for same power as bulb/spot/... + diff --git a/presets/pov/lamp/23_(30000K)_40W_Black_Light_Bulb.py b/presets/pov/lamp/23_(30000K)_40W_Black_Light_Bulb.py new file mode 100644 index 00000000..baad8234 --- /dev/null +++ b/presets/pov/lamp/23_(30000K)_40W_Black_Light_Bulb.py @@ -0,0 +1,10 @@ +#Starting from 1918 (World War I Wood's glass) + +import bpy +bpy.context.object.data.type = 'POINT' +lampdata = bpy.context.object.data + +lampdata.color = (0.6549019813537598, 0.0, 1.0) +lampdata.energy = 1.86515#100/21.446 #lumen values/21.446 or lux when available used as a basis +lampdata.distance = 0.01 +lampdata.falloff_type = 'INVERSE_SQUARE' diff --git a/presets/pov/lamp/24_(1850K)_Candle.py b/presets/pov/lamp/24_(1850K)_Candle.py new file mode 100644 index 00000000..5d7ac61d --- /dev/null +++ b/presets/pov/lamp/24_(1850K)_Candle.py @@ -0,0 +1,24 @@ +#Starting from 1825 (stearin) + +import bpy +bpy.context.object.data.type = 'POINT' +lampdata = bpy.context.object.data + +lampdata.color = (1.0, 0.7176470756530762, 0.2980392277240753) +#lampdata.color = (1.0, 0.5764706134796143, 0.16078431904315948) +#http://terpconnect.umd.edu/~pbs/2011-Sunderland-et-al-PCI.pdf +#https://blog.1000bulbs.com/home/whats-the-difference-between-candela-lux-and-lumens +#Environment Typical Lux +#Hospital Theatre 1,000 +#Supermarket, Workshop, Sports Hall 750 +#Office, Show Rooms, Laboratories, Kitchens 500 +#Warehouse Loading Bays 300 to 400 +#School Classroom, University Lecture Hall 250 +#Lobbies, Public Corridors, Stairwells 200 +#Warehouse Aisles 100 to 200 +#Homes, Theatres 150 +#Family Living Room 50 +#Sunset & Sunrise 400 lux +lampdata.energy = 2.0 #two times lux value +lampdata.distance = 0.004 +lampdata.falloff_type = 'INVERSE_SQUARE' diff --git a/presets/pov/radiosity/01_Debug.py b/presets/pov/radiosity/01_Debug.py new file mode 100644 index 00000000..420dbd7e --- /dev/null +++ b/presets/pov/radiosity/01_Debug.py @@ -0,0 +1,20 @@ +import bpy +scene = bpy.context.scene + +scene.pov.radio_display_advanced = True +scene.pov.radio_adc_bailout = 0.005 +scene.pov.radio_always_sample = False +scene.pov.radio_brightness = 1.0 +scene.pov.radio_count = 10 +scene.pov.radio_error_bound = 0.3 +scene.pov.radio_gray_threshold = 0.0 +scene.pov.radio_low_error_factor = 0.8 +scene.pov.radio_media = False +scene.pov.radio_subsurface = False +scene.pov.radio_minimum_reuse = 0.015 +scene.pov.radio_maximum_reuse = 0.2 +scene.pov.radio_nearest_count = 1 +scene.pov.radio_normal = False +scene.pov.radio_recursion_limit = 1 +scene.pov.radio_pretrace_start = 0.08 +scene.pov.radio_pretrace_end = 0.01 diff --git a/presets/pov/radiosity/02_Fast.py b/presets/pov/radiosity/02_Fast.py new file mode 100644 index 00000000..08864c83 --- /dev/null +++ b/presets/pov/radiosity/02_Fast.py @@ -0,0 +1,19 @@ +import bpy +scene = bpy.context.scene + +scene.pov.radio_adc_bailout = 0.005 +scene.pov.radio_always_sample = False +scene.pov.radio_brightness = 1.0 +scene.pov.radio_count = 80 +scene.pov.radio_error_bound = 0.4 +scene.pov.radio_gray_threshold = 0.0 +scene.pov.radio_low_error_factor = 0.9 +scene.pov.radio_media = False +scene.pov.radio_subsurface = False +scene.pov.radio_minimum_reuse = 0.025 +scene.pov.radio_maximum_reuse = 0.2 +scene.pov.radio_nearest_count = 5 +scene.pov.radio_normal = False +scene.pov.radio_recursion_limit = 1 +scene.pov.radio_pretrace_start = 0.08 +scene.pov.radio_pretrace_end = 0.02 diff --git a/presets/pov/radiosity/03_Normal.py b/presets/pov/radiosity/03_Normal.py new file mode 100644 index 00000000..b66a1b2a --- /dev/null +++ b/presets/pov/radiosity/03_Normal.py @@ -0,0 +1,20 @@ +import bpy +scene = bpy.context.scene + +scene.pov.radio_display_advanced = True +scene.pov.radio_adc_bailout = 0.005 +scene.pov.radio_always_sample = False +scene.pov.radio_brightness = 1.0 +scene.pov.radio_count = 200 +scene.pov.radio_error_bound = 0.3 +scene.pov.radio_gray_threshold = 0.0 +scene.pov.radio_low_error_factor = 0.75 +scene.pov.radio_media = False +scene.pov.radio_subsurface = False +scene.pov.radio_minimum_reuse = 0.017 +scene.pov.radio_maximum_reuse = 0.2 +scene.pov.radio_nearest_count = 7 +scene.pov.radio_normal = False +scene.pov.radio_recursion_limit = 1 +scene.pov.radio_pretrace_start = 0.08 +scene.pov.radio_pretrace_end = 0.01 diff --git a/presets/pov/radiosity/04_Two_Bounces.py b/presets/pov/radiosity/04_Two_Bounces.py new file mode 100644 index 00000000..2072247a --- /dev/null +++ b/presets/pov/radiosity/04_Two_Bounces.py @@ -0,0 +1,20 @@ +import bpy +scene = bpy.context.scene + +scene.pov.radio_display_advanced = True +scene.pov.radio_adc_bailout = 0.005 +scene.pov.radio_always_sample = False +scene.pov.radio_brightness = 1.0 +scene.pov.radio_count = 200 +scene.pov.radio_error_bound = 0.3 +scene.pov.radio_gray_threshold = 0.0 +scene.pov.radio_low_error_factor = 0.75 +scene.pov.radio_media = False +scene.pov.radio_subsurface = False +scene.pov.radio_minimum_reuse = 0.017 +scene.pov.radio_maximum_reuse = 0.2 +scene.pov.radio_nearest_count = 7 +scene.pov.radio_normal = False +scene.pov.radio_recursion_limit = 2 +scene.pov.radio_pretrace_start = 0.08 +scene.pov.radio_pretrace_end = 0.01 diff --git a/presets/pov/radiosity/05_Final.py b/presets/pov/radiosity/05_Final.py new file mode 100644 index 00000000..52a9d5c3 --- /dev/null +++ b/presets/pov/radiosity/05_Final.py @@ -0,0 +1,20 @@ +import bpy +scene = bpy.context.scene + +scene.pov.radio_display_advanced = True +scene.pov.radio_adc_bailout = 0.005 +scene.pov.radio_always_sample = False +scene.pov.radio_brightness = 1.0 +scene.pov.radio_count = 800 +scene.pov.radio_error_bound = 0.2 +scene.pov.radio_gray_threshold = 0.0 +scene.pov.radio_low_error_factor = 0.7 +scene.pov.radio_media = False +scene.pov.radio_subsurface = False +scene.pov.radio_minimum_reuse = 0.01 +scene.pov.radio_maximum_reuse = 0.2 +scene.pov.radio_nearest_count = 9 +scene.pov.radio_normal = False +scene.pov.radio_recursion_limit = 1 +scene.pov.radio_pretrace_start = 0.08 +scene.pov.radio_pretrace_end = 0.01 diff --git a/presets/pov/radiosity/06_Outdoor_Low_Quality.py b/presets/pov/radiosity/06_Outdoor_Low_Quality.py new file mode 100644 index 00000000..53553264 --- /dev/null +++ b/presets/pov/radiosity/06_Outdoor_Low_Quality.py @@ -0,0 +1,20 @@ +import bpy +scene = bpy.context.scene + +scene.pov.radio_display_advanced = True +scene.pov.radio_adc_bailout = 0.005 +scene.pov.radio_always_sample = False +scene.pov.radio_brightness = 1.0 +scene.pov.radio_count = 80 +scene.pov.radio_error_bound = 0.6 +scene.pov.radio_gray_threshold = 0.0 +scene.pov.radio_low_error_factor = 0.8 +scene.pov.radio_media = False +scene.pov.radio_subsurface = False +scene.pov.radio_minimum_reuse = 0.015 +scene.pov.radio_maximum_reuse = 0.2 +scene.pov.radio_nearest_count = 4 +scene.pov.radio_normal = False +scene.pov.radio_recursion_limit = 1 +scene.pov.radio_pretrace_start = 0.08 +scene.pov.radio_pretrace_end = 0.01 diff --git a/presets/pov/radiosity/07_Outdoor_High_Quality.py b/presets/pov/radiosity/07_Outdoor_High_Quality.py new file mode 100644 index 00000000..8b06dabe --- /dev/null +++ b/presets/pov/radiosity/07_Outdoor_High_Quality.py @@ -0,0 +1,20 @@ +import bpy +scene = bpy.context.scene + +scene.pov.radio_display_advanced = True +scene.pov.radio_adc_bailout = 0.005 +scene.pov.radio_always_sample = False +scene.pov.radio_brightness = 1.0 +scene.pov.radio_count = 500 +scene.pov.radio_error_bound = 0.1 +scene.pov.radio_gray_threshold = 0.0 +scene.pov.radio_low_error_factor = 0.5 +scene.pov.radio_media = False +scene.pov.radio_subsurface = False +scene.pov.radio_minimum_reuse = 0.015 +scene.pov.radio_maximum_reuse = 0.2 +scene.pov.radio_nearest_count = 7 +scene.pov.radio_normal = False +scene.pov.radio_recursion_limit = 1 +scene.pov.radio_pretrace_start = 0.08 +scene.pov.radio_pretrace_end = 0.004 diff --git a/presets/pov/radiosity/08_Outdoor_(Sun)Light.py b/presets/pov/radiosity/08_Outdoor_(Sun)Light.py new file mode 100644 index 00000000..94448c1f --- /dev/null +++ b/presets/pov/radiosity/08_Outdoor_(Sun)Light.py @@ -0,0 +1,20 @@ +import bpy +scene = bpy.context.scene + +scene.pov.radio_display_advanced = True +scene.pov.radio_adc_bailout = 0.005 +scene.pov.radio_always_sample = False +scene.pov.radio_brightness = 1.0 +scene.pov.radio_count = 50 +scene.pov.radio_error_bound = 0.8 +scene.pov.radio_gray_threshold = 0.0 +scene.pov.radio_low_error_factor = 0.9 +scene.pov.radio_media = False +scene.pov.radio_subsurface = False +scene.pov.radio_minimum_reuse = 0.015 +scene.pov.radio_maximum_reuse = 0.2 +scene.pov.radio_nearest_count = 4 +scene.pov.radio_normal = False +scene.pov.radio_recursion_limit = 1 +scene.pov.radio_pretrace_start = 0.08 +scene.pov.radio_pretrace_end = 0.01 diff --git a/presets/pov/radiosity/09_Indoor_Low_Quality.py b/presets/pov/radiosity/09_Indoor_Low_Quality.py new file mode 100644 index 00000000..e5fa7bb6 --- /dev/null +++ b/presets/pov/radiosity/09_Indoor_Low_Quality.py @@ -0,0 +1,20 @@ +import bpy +scene = bpy.context.scene + +scene.pov.radio_display_advanced = True +scene.pov.radio_adc_bailout = 0.005 +scene.pov.radio_always_sample = False +scene.pov.radio_brightness = 1.0 +scene.pov.radio_count = 80 +scene.pov.radio_error_bound = 0.7 +scene.pov.radio_gray_threshold = 0.0 +scene.pov.radio_low_error_factor = 0.8 +scene.pov.radio_media = False +scene.pov.radio_subsurface = False +scene.pov.radio_minimum_reuse = 0.015 +scene.pov.radio_maximum_reuse = 0.2 +scene.pov.radio_nearest_count = 5 +scene.pov.radio_normal = False +scene.pov.radio_recursion_limit = 2 +scene.pov.radio_pretrace_start = 0.08 +scene.pov.radio_pretrace_end = 0.01 diff --git a/presets/pov/radiosity/10_Indoor_High_Quality.py b/presets/pov/radiosity/10_Indoor_High_Quality.py new file mode 100644 index 00000000..5a752f6c --- /dev/null +++ b/presets/pov/radiosity/10_Indoor_High_Quality.py @@ -0,0 +1,20 @@ +import bpy +scene = bpy.context.scene + +scene.pov.radio_display_advanced = True +scene.pov.radio_adc_bailout = 0.005 +scene.pov.radio_always_sample = False +scene.pov.radio_brightness = 1.0 +scene.pov.radio_count = 400 +scene.pov.radio_error_bound = 0.15 +scene.pov.radio_gray_threshold = 0.0 +scene.pov.radio_low_error_factor = 0.5 +scene.pov.radio_media = False +scene.pov.radio_subsurface = False +scene.pov.radio_minimum_reuse = 0.015 +scene.pov.radio_maximum_reuse = 0.2 +scene.pov.radio_nearest_count = 8 +scene.pov.radio_normal = False +scene.pov.radio_recursion_limit = 3 +scene.pov.radio_pretrace_start = 0.08 +scene.pov.radio_pretrace_end = 0.004 diff --git a/presets/pov/world/1_Clear_Blue_Sky.py b/presets/pov/world/1_Clear_Blue_Sky.py new file mode 100644 index 00000000..0dcd5e05 --- /dev/null +++ b/presets/pov/world/1_Clear_Blue_Sky.py @@ -0,0 +1,36 @@ +import bpy +scene = bpy.context.scene + +scene.world.use_sky_blend = True +#below multiplied by two for a better proportion Clear vs Overcast sky +#since Clear sky is 19807 lux vs 2000 for overcast (sun is min 32000 max 100000) +#http://www.pssurvival.com/PS/Lighting/Typical_LUX_Intensities_for_Day_and_Night-2017.pdf +#https://en.wikipedia.org/wiki/Daylight +#https://www.engineeringtoolbox.com/light-level-rooms-d_708.html +#https://www.cactus2000.de/fr/unit/masslux.shtml +#https://blendergrid.com/news/cycles-physically-correct-brightness +#researched result blue is + #Hue: 0.6 + #Saturation: 0.533 + #Lightness: 0.7 +#put scattering scale at 0.0002 and scattering color rgb <0.2061, 0.3933, 1.0> +#with very small value like round rgb 0.00002 0.00002 0.00008 +#Sky color at zenith, sun 90° elevation = hsl <0.6, 0.533, 0.698> +#Ground color = rgb <0.996, 0.965, 0.855> = hsl <0.128, 0.141, 0.996> +#Ground Brighness = 0.996 + +scene.world.horizon_color = (0.047, 0.034, 0.025) #24000 or 22000 lux roughly equals (sun+sky)/5 +scene.world.zenith_color = (0.006, 0.013, 0.033) #19807 lux roughly equals hign noon Sun / 5 +scene.world.ambient_color = (0.0, 0.0, 0.0) +scene.world.mist_settings.use_mist = False +scene.world.mist_settings.intensity = 0.0 +scene.world.mist_settings.depth = 25.0 +scene.world.mist_settings.start = 5.0 +scene.pov.media_enable = True +scene.pov.media_scattering_type = '4' +scene.pov.media_samples = 35 +scene.pov.media_diffusion_scale = (0.00002) +scene.pov.media_diffusion_color = (0.000001, 0.000002, 0.000005) +scene.pov.media_absorption_scale = (0.00002) +scene.pov.media_absorption_color = (0.0000006067, 0.0000007939, 0.0)#up to 0.00007 +scene.pov.media_eccentricity = 0.0 diff --git a/presets/pov/world/2_Partly_Hazy_Sky.py b/presets/pov/world/2_Partly_Hazy_Sky.py new file mode 100644 index 00000000..2f6b628d --- /dev/null +++ b/presets/pov/world/2_Partly_Hazy_Sky.py @@ -0,0 +1,36 @@ +import bpy +scene = bpy.context.scene + +scene.world.use_sky_blend = True +#below multiplied by two for a better proportion Clear vs Overcast sky +#since Clear sky is 19807 lux vs 2000 for overcast (sun is min 32000 max 100000) +#http://www.pssurvival.com/PS/Lighting/Typical_LUX_Intensities_for_Day_and_Night-2017.pdf +#https://en.wikipedia.org/wiki/Daylight +#https://www.engineeringtoolbox.com/light-level-rooms-d_708.html +#https://www.cactus2000.de/fr/unit/masslux.shtml +#https://blendergrid.com/news/cycles-physically-correct-brightness +#researched result blue is + #Hue: 0.6 + #Saturation: 0.533 + #Lightness: 0.7 +#put scattering scale at 0.0002 and scattering color rgb <0.2061, 0.3933, 1.0> +#with very small value like round rgb 0.00002 0.00002 0.00008 +#Sky color at zenith, sun 90° elevation = hsl <0.6, 0.533, 0.698> +#Ground color = rgb <0.996, 0.965, 0.855> = hsl <0.128, 0.141, 0.996> +#Ground Brighness = 0.996 + +scene.world.horizon_color = (0.380, 0.262, 0.183) #24000 or 22000 lux roughly equals (sun+sky)/5 + urban light pollution +scene.world.zenith_color = (0.006, 0.013, 0.033) #19807 lux roughly equals hign noon Sun / 5 +scene.world.ambient_color = (0.0, 0.0, 0.0) +scene.world.mist_settings.use_mist = False +scene.world.mist_settings.intensity = 0.0 +scene.world.mist_settings.depth = 25.0 +scene.world.mist_settings.start = 5.0 +scene.pov.media_enable = True +scene.pov.media_scattering_type = '4' +scene.pov.media_samples = 35 +scene.pov.media_diffusion_scale = (0.00002) +scene.pov.media_diffusion_color = (0.000001, 0.000002, 0.000005) +scene.pov.media_absorption_scale = (0.00002) +scene.pov.media_absorption_color = (0.0000006067, 0.0000007939, 0.0)#up to 0.00007 +scene.pov.media_eccentricity = 0.0 diff --git a/presets/pov/world/3_Overcast_Sky.py b/presets/pov/world/3_Overcast_Sky.py new file mode 100644 index 00000000..013a5d40 --- /dev/null +++ b/presets/pov/world/3_Overcast_Sky.py @@ -0,0 +1,20 @@ +import bpy +scene = bpy.context.scene + +scene.world.use_sky_blend = True +scene.world.horizon_color = (0.477, 0.536, 0.604) +#below divided by ten for a better proportion Clear vs Overcast sky +#since Clear sky is 20000 lux vs 2000 up to 10000 for overcast +scene.world.zenith_color = (0.034, 0.043, 0.047) +scene.world.ambient_color = (0.0, 0.0, 0.0) +scene.world.mist_settings.use_mist = False +scene.world.mist_settings.intensity = 0.0 +scene.world.mist_settings.depth = 25.0 +scene.world.mist_settings.start = 5.0 +scene.pov.media_enable = False +scene.pov.media_scattering_type = '1' +scene.pov.media_samples = 35 +scene.pov.media_diffusion_scale = (1.0) +scene.pov.media_diffusion_color = (0.58, 0.66, 0.75) +scene.pov.media_absorption_color = (0.0, 0.0, 0.0) +scene.pov.media_eccentricity = 0.0 diff --git a/presets/pov/world/4_Cartoony_Sky.py b/presets/pov/world/4_Cartoony_Sky.py new file mode 100644 index 00000000..e3c552c0 --- /dev/null +++ b/presets/pov/world/4_Cartoony_Sky.py @@ -0,0 +1,19 @@ +import bpy +scene = bpy.context.scene + +scene.world.use_sky_blend = True +#below multiplied by two for a better proportion Clear vs Overcast sky +#since Clear sky is 20000 lux vs 2000 for overcast +scene.world.horizon_color = (0.350*2, 0.611*2, 1.0*2) +scene.world.zenith_color = (0.05000000074505806*2, 0.125*2, 0.5*2) +scene.world.ambient_color = (0.0, 0.0, 0.0) +scene.world.mist_settings.use_mist = False +scene.world.mist_settings.intensity = 0.0 +scene.world.mist_settings.depth = 25.0 +scene.world.mist_settings.start = 5.0 +scene.pov.media_enable = False +scene.pov.media_scattering_type = '4' +scene.pov.media_samples = 35 +scene.pov.media_diffusion_color = (0.20000000298023224, 0.4000000059604645, 1.0) +scene.pov.media_absorption_color = (0.0, 0.0, 0.0) +scene.pov.media_eccentricity = 0.0
\ No newline at end of file diff --git a/presets/pov/world/5_Under_Water.py b/presets/pov/world/5_Under_Water.py new file mode 100644 index 00000000..e6659016 --- /dev/null +++ b/presets/pov/world/5_Under_Water.py @@ -0,0 +1,19 @@ +import bpy +scene = bpy.context.scene + +scene.world.use_sky_blend = True +#below multiplied by two for a better proportion Clear vs Overcast sky +#since Clear sky is 20000 lux vs 2000 for overcast +scene.world.horizon_color = (0.0, 0.0, 0.0) +scene.world.zenith_color = (0.250980406999588, 0.6117647290229797, 1.0) +scene.world.ambient_color = (0.0, 0.0, 0.0) +scene.world.mist_settings.use_mist = False +scene.world.mist_settings.intensity = 0.0 +scene.world.mist_settings.depth = 25.0 +scene.world.mist_settings.start = 5.0 +scene.pov.media_enable = True +scene.pov.media_scattering_type = '5' +scene.pov.media_samples = 35 +scene.pov.media_diffusion_color = (0.000034, 0.000034, 0.000017) +scene.pov.media_absorption_color = (0.00000455, 0.00000165, 0.00000031) +scene.pov.media_eccentricity = 0.7
\ No newline at end of file diff --git a/render_povray/__init__.py b/render_povray/__init__.py index cfb45f5a..90d80f13 100644 --- a/render_povray/__init__.py +++ b/render_povray/__init__.py @@ -19,14 +19,13 @@ # <pep8 compliant> bl_info = { - "name": "POVRAY-3.7", + "name": "POV-3.7", "author": "Campbell Barton, Silvio Falcinelli, Maurice Raybaud, " "Constantin Rahn, Bastien Montagne, Leonid Desyatkov", - "version": (0, 0, 9), - "blender": (2, 75, 0), + "version": (0, 1, 0), + "blender": (2, 79, 0), "location": "Render > Engine > POV-Ray 3.7", "description": "Basic POV-Ray 3.7 integration for blender", - "warning": "this script is RC", "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" "Scripts/Render/POV-Ray", "category": "Render", @@ -44,6 +43,7 @@ else: #import addon_utils # To use some other addons import nodeitems_utils #for Nodes from nodeitems_utils import NodeCategory, NodeItem #for Nodes + from bl_operators.presets import AddPresetBase from bpy.types import ( AddonPreferences, PropertyGroup, @@ -73,6 +73,11 @@ def string_strip_hyphen(name): # Scene POV properties. ############################################################################### class RenderPovSettingsScene(PropertyGroup): + #Linux SDL-window enable + sdl_window_enable = BoolProperty( + name="Enable SDL window", + description="Enable the SDL window in Linux OS", + default=True) # File Options text_block = StringProperty( name="Text Scene Name", @@ -121,7 +126,7 @@ class RenderPovSettingsScene(PropertyGroup): radio_enable = BoolProperty( name="Enable Radiosity", description="Enable POV-Rays radiosity calculation", - default=False) + default=True) radio_display_advanced = BoolProperty( name="Advanced Options", @@ -132,19 +137,88 @@ class RenderPovSettingsScene(PropertyGroup): name="Enable Media", description="Enable POV-Rays atmospheric media", default=False) + media_samples = IntProperty( name="Samples", description="Number of samples taken from camera to first object " "encountered along ray path for media calculation", min=1, max=100, default=35) - - media_color = FloatVectorProperty( - name="Media Color", description="The atmospheric media color", + + media_scattering_type = EnumProperty( + name="Scattering Type", + description="Scattering model", + items=(('1', "1 Isotropic", "The simplest form of scattering because" + " it is independent of direction."), + ('2', "2 Mie haze ", "For relatively small particles such as " + "minuscule water droplets of fog, cloud " + "particles, and particles responsible " + "for the polluted sky. In this model the" + " scattering is extremely directional in" + " the forward direction i.e. the amount " + "of scattered light is largest when the " + "incident light is anti-parallel to the " + "viewing direction (the light goes " + "directly to the viewer). It is smallest" + " when the incident light is parallel to" + " the viewing direction. "), + ('3', "3 Mie murky", "Like haze but much more directional"), + ('4', "4 Rayleigh", "For extremely small particles such as " + "molecules of the air. The amount of " + "scattered light depends on the incident" + " light angle. It is largest when the " + "incident light is parallel or " + "anti-parallel to the viewing direction " + "and smallest when the incident light is " + "perpendicular to viewing direction."), + ('5', "5 Henyey-Greenstein", "The default eccentricity value " + "of zero defines isotropic " + "scattering while positive " + "values lead to scattering in " + "the direction of the light and " + "negative values lead to " + "scattering in the opposite " + "direction of the light. Larger " + "values of e (or smaller values " + "in the negative case) increase " + "the directional property of the" + " scattering.")), + default='1') + + media_diffusion_scale = FloatProperty( + name="Scale", description="Scale factor of Media Diffusion Color", + precision=12, step=0.00000001, min=0.000000001, max=1.0, + default=(1.0)) + + media_diffusion_color = FloatVectorProperty( + name="Media Diffusion Color", description="The atmospheric media color", precision=4, step=0.01, min=0, soft_max=1, default=(0.001, 0.001, 0.001), options={'ANIMATABLE'}, subtype='COLOR') + media_absorption_scale = FloatProperty( + name="Scale", description="Scale factor of Media Absorption Color. " + "use 1/depth of media volume in meters", + precision=12, step=0.000001, min=0.000000001, max=1.0, + default=(0.00002)) + + media_absorption_color = FloatVectorProperty( + name="Media Absorption Color", description="The atmospheric media absorption color", + precision=4, step=0.01, min=0, soft_max=1, + default=(0.0, 0.0, 0.0), + options={'ANIMATABLE'}, + subtype='COLOR') + + media_eccentricity = FloatProperty( + name="Media Eccenticity Factor", description="Positive values lead" + " to scattering in the direction of the light and negative " + "values lead to scattering in the opposite direction of the " + "light. Larger values of e (or smaller values in the negative" + " case) increase the directional property of the scattering.", + precision=2, step=0.01, min=-1.0, max=1.0, + default=(0.0), + options={'ANIMATABLE'}) + baking_enable = BoolProperty( name="Enable Baking", description="Enable POV-Rays texture baking", @@ -377,7 +451,7 @@ class RenderPovSettingsScene(PropertyGroup): description="", maxlen=1024, subtype="FILE_PATH") - + #########RADIOSITY######## radio_adc_bailout = FloatProperty( name="ADC Bailout", description="The adc_bailout for radiosity rays. Use " @@ -422,7 +496,7 @@ class RenderPovSettingsScene(PropertyGroup): radio_media = BoolProperty( name="Media", description="Radiosity estimation can be affected by media", - default=False) + default=True) radio_subsurface = BoolProperty( name="Subsurface", description="Radiosity estimation can be affected by Subsurface Light Transport", @@ -468,7 +542,6 @@ class RenderPovSettingsScene(PropertyGroup): "in the mosaic preview last pass", min=0.000925, max=1.00, soft_min=0.01, soft_max=1.00, default=0.04, precision=3) - ############################################################################### # Material POV properties. ############################################################################### @@ -2199,20 +2272,14 @@ class PovrayPreferences(AddonPreferences): - - - - - - - - - def register(): bpy.utils.register_module(__name__) bpy.types.INFO_MT_add.prepend(ui.menu_func_add) bpy.types.INFO_MT_file_import.append(ui.menu_func_import) bpy.types.TEXT_MT_templates.append(ui.menu_func_templates) + bpy.types.RENDER_PT_povray_radiosity.prepend(ui.rad_panel_func) + bpy.types.LAMP_PT_POV_lamp.prepend(ui.lamp_panel_func) + bpy.types.WORLD_PT_world.prepend(ui.world_panel_func) # was used for parametric objects but made the other addon unreachable on # unregister for other tools to use created a user action call instead #addon_utils.enable("add_mesh_extra_objects", default_set=False, persistent=True) @@ -2244,6 +2311,9 @@ def unregister(): #bpy.types.TEXTURE_PT_context_texture.remove(TEXTURE_PT_povray_type) #addon_utils.disable("add_mesh_extra_objects", default_set=False) + bpy.types.WORLD_PT_world.remove(ui.world_panel_func) + bpy.types.LAMP_PT_POV_lamp.remove(ui.lamp_panel_func) + bpy.types.RENDER_PT_povray_radiosity.remove(ui.rad_panel_func) bpy.types.TEXT_MT_templates.remove(ui.menu_func_templates) bpy.types.INFO_MT_file_import.remove(ui.menu_func_import) bpy.types.INFO_MT_add.remove(ui.menu_func_add) diff --git a/render_povray/render.py b/render_povray/render.py index d3695780..9838f25d 100644 --- a/render_povray/render.py +++ b/render_povray/render.py @@ -186,13 +186,17 @@ def safety(name, Level): ##############end safety string name material ##############################EndSF########################### +csg_list = [] + def is_renderable(scene, ob): - return (ob.is_visible(scene) and not ob.hide_render) + return (ob.is_visible(scene) and not ob.hide_render and ob not in csg_list) def renderable_objects(scene): return [ob for ob in bpy.data.objects if is_renderable(scene, ob)] +def no_renderable_objects(scene): + return [ob for ob in csg_list] tabLevel = 0 unpacked_images=[] @@ -628,7 +632,7 @@ def write_pov(filename, scene=None, info_callback=None): tabWrite("fade_distance %.6f\n" % (lamp.distance / 2.0)) # Area lights have no falloff type, so always use blenders lamp quad equivalent # for those? - tabWrite("fade_power %d\n" % 0) + tabWrite("fade_power %d\n" % 2) size_x = lamp.size samples_x = lamp.shadow_ray_samples_x if lamp.shape == 'SQUARE': @@ -1446,7 +1450,7 @@ def write_pov(filename, scene=None, info_callback=None): file.write(' BuildWriteMesh2(VecArr, NormArr, UVArr, Iter_U, Iter_V, FileName)\n') file.write(' #end\n') file.write('#end\n\n') - # Empty curves + # Empty curves if len(ob.data.splines)==0: tabWrite("\n//dummy sphere to represent empty curve location\n") tabWrite("#declare %s =\n"%dataname) @@ -1793,7 +1797,7 @@ def write_pov(filename, scene=None, info_callback=None): # objectNames = {} DEF_OBJ_NAME = "Default" - def exportMeshes(scene, sel): + def exportMeshes(scene, sel, csg): # obmatslist = [] # def hasUniqueMaterial(): # # Grab materials attached to object instances ... @@ -2416,7 +2420,7 @@ def write_pov(filename, scene=None, info_callback=None): tabWrite("#declare %s = plane{ <0,0,1>,1\n"%povdataname) povMatName = "Default_texture" if ob.active_material: - #povMatName = string_strip_hyphen(bpy.path.clean_name(ob.active_material.name)) + #povMatName = string_strip_hyphen(bpy.path.clean_name(ob.active_material.name)) try: material = ob.active_material writeObjectMaterial(material, ob) @@ -3009,7 +3013,7 @@ def write_pov(filename, scene=None, info_callback=None): # POV object modifiers such as # hollow / sturm / double_illuminate etc. write_object_modifiers(scene,ob,file) - + #Importance for radiosity sampling added here: tabWrite("radiosity { \n") tabWrite("importance %3g \n" % importance) @@ -3254,7 +3258,7 @@ def write_pov(filename, scene=None, info_callback=None): ob.pov.inside_vector[1], ob.pov.inside_vector[2])) onceCSG = 1 - + if me.materials: try: material = me.materials[0] # dodgy @@ -3277,38 +3281,69 @@ def write_pov(filename, scene=None, info_callback=None): bpy.data.meshes.remove(me) - duplidata_ref = [] - for ob in sel: - #matrix = global_matrix * ob.matrix_world - if ob.is_duplicator: - tabWrite("\n//--DupliObjects in %s--\n\n"% ob.name) - ob.dupli_list_create(scene) - dup = "" - if ob.is_modified(scene, 'RENDER'): - #modified object always unique so using object name rather than data name - dup = "#declare OB%s = union{\n" %(string_strip_hyphen(bpy.path.clean_name(ob.name))) + if csg: + duplidata_ref = [] + for ob in sel: + #matrix = global_matrix * ob.matrix_world + if ob.is_duplicator: + tabWrite("\n//--DupliObjects in %s--\n\n"% ob.name) + ob.dupli_list_create(scene) + dup = "" + if ob.is_modified(scene, 'RENDER'): + #modified object always unique so using object name rather than data name + dup = "#declare OB%s = union{\n" %(string_strip_hyphen(bpy.path.clean_name(ob.name))) + else: + dup = "#declare DATA%s = union{\n" %(string_strip_hyphen(bpy.path.clean_name(ob.name))) + for eachduplicate in ob.dupli_list: + duplidataname = "OB"+string_strip_hyphen(bpy.path.clean_name(bpy.data.objects[eachduplicate.object.name].data.name)) + dup += ("\tobject {\n\t\tDATA%s\n\t\t%s\t}\n" %(string_strip_hyphen(bpy.path.clean_name(bpy.data.objects[eachduplicate.object.name].data.name)), MatrixAsPovString(ob.matrix_world.inverted() * eachduplicate.matrix))) + #add object to a list so that it is not rendered for some dupli_types + if ob.dupli_type not in {'GROUP'} and duplidataname not in duplidata_ref: + duplidata_ref.append(duplidataname) #older key [string_strip_hyphen(bpy.path.clean_name("OB"+ob.name))] + dup += "}\n" + ob.dupli_list_clear() + tabWrite(dup) else: - dup = "#declare DATA%s = union{\n" %(string_strip_hyphen(bpy.path.clean_name(ob.name))) - for eachduplicate in ob.dupli_list: - duplidataname = "OB"+string_strip_hyphen(bpy.path.clean_name(bpy.data.objects[eachduplicate.object.name].data.name)) - dup += ("\tobject {\n\t\tDATA%s\n\t\t%s\t}\n" %(string_strip_hyphen(bpy.path.clean_name(bpy.data.objects[eachduplicate.object.name].data.name)), MatrixAsPovString(ob.matrix_world.inverted() * eachduplicate.matrix))) - #add object to a list so that it is not rendered for some dupli_types - if ob.dupli_type not in {'GROUP'} and duplidataname not in duplidata_ref: - duplidata_ref.append(duplidataname) #older key [string_strip_hyphen(bpy.path.clean_name("OB"+ob.name))] - dup += "}\n" - ob.dupli_list_clear() - tabWrite(dup) - else: - continue - print(duplidata_ref) - for data_name, inst in data_ref.items(): - for ob_name, matrix_str in inst: - if ob_name not in duplidata_ref: #.items() for a dictionary - tabWrite("\n//----Blender Object Name:%s----\n" % ob_name) - tabWrite("object { \n") - tabWrite("%s\n" % data_name) - tabWrite("%s\n" % matrix_str) - tabWrite("}\n") + continue + print(duplidata_ref) + for data_name, inst in data_ref.items(): + for ob_name, matrix_str in inst: + if ob_name not in duplidata_ref: #.items() for a dictionary + tabWrite("\n//----Blender Object Name:%s----\n" % ob_name) + if ob.pov.object_as == '': + tabWrite("object { \n") + tabWrite("%s\n" % data_name) + tabWrite("%s\n" % matrix_str) + tabWrite("}\n") + else: + no_boolean = True + for mod in ob.modifiers: + if mod.type == 'BOOLEAN': + operation = None + no_boolean = False + if mod.operation == 'INTERSECT': + operation = 'intersection' + else: + operation = mod.operation.lower() + mod_ob_name = string_strip_hyphen(bpy.path.clean_name(mod.object.name)) + mod_matrix = global_matrix * mod.object.matrix_world + mod_ob_matrix = MatrixAsPovString(mod_matrix) + tabWrite("%s { \n"%operation) + tabWrite("object { \n") + tabWrite("%s\n" % data_name) + tabWrite("%s\n" % matrix_str) + tabWrite("}\n") + tabWrite("object { \n") + tabWrite("%s\n" % ('DATA'+ mod_ob_name)) + tabWrite("%s\n" % mod_ob_matrix) + tabWrite("}\n") + tabWrite("}\n") + break + if no_boolean: + tabWrite("object { \n") + tabWrite("%s\n" % data_name) + tabWrite("%s\n" % matrix_str) + tabWrite("}\n") def exportWorld(world): render = scene.render @@ -3444,18 +3479,33 @@ def write_pov(filename, scene=None, info_callback=None): if mist.use_mist: tabWrite("fog {\n") - tabWrite("distance %.6f\n" % mist.depth) + if mist.falloff=='LINEAR': + tabWrite("distance %.6f\n" % ((mist.start+mist.depth)*0.368)) + elif mist.falloff=='QUADRATIC': # n**2 or squrt(n)? + tabWrite("distance %.6f\n" % ((mist.start+mist.depth)**2*0.368)) + elif mist.falloff=='INVERSE_QUADRATIC': # n**2 or squrt(n)? + tabWrite("distance %.6f\n" % ((mist.start+mist.depth)**2*0.368)) tabWrite("color rgbt<%.3g, %.3g, %.3g, %.3g>\n" % \ (*world.horizon_color, 1.0 - mist.intensity)) - #tabWrite("fog_offset %.6f\n" % mist.start) - #tabWrite("fog_alt 5\n") + #tabWrite("fog_offset %.6f\n" % mist.start) #create a pov property to prepend + #tabWrite("fog_alt %.6f\n" % mist.height) #XXX right? #tabWrite("turbulence 0.2\n") #tabWrite("turb_depth 0.3\n") - tabWrite("fog_type 1\n") + tabWrite("fog_type 1\n") #type2 for height tabWrite("}\n") if scene.pov.media_enable: tabWrite("media {\n") - tabWrite("scattering { 1, rgb <%.4g, %.4g, %.4g>}\n" % scene.pov.media_color[:]) + tabWrite("scattering { %d, rgb %.12f*<%.4g, %.4g, %.4g>\n" % \ + (int(scene.pov.media_scattering_type), + (scene.pov.media_diffusion_scale), + *(scene.pov.media_diffusion_color[:]))) + if scene.pov.media_scattering_type == '5': + tabWrite("eccentricity %.3g\n" % scene.pov.media_eccentricity) + tabWrite("}\n") + tabWrite("absorption %.12f*<%.4g, %.4g, %.4g>\n" % \ + (scene.pov.media_absorption_scale, + *(scene.pov.media_absorption_color[:]))) + tabWrite("\n") tabWrite("samples %.d\n" % scene.pov.media_samples) tabWrite("}\n") @@ -3559,7 +3609,7 @@ def write_pov(filename, scene=None, info_callback=None): file.write(txt.as_string()) file.write("\n") - sel = renderable_objects(scene) + #sel = renderable_objects(scene) #removed for booleans if comments: file.write("//----------------------------------------------\n" \ "//--Exported with POV-Ray exporter for Blender--\n" \ @@ -3602,6 +3652,20 @@ def write_pov(filename, scene=None, info_callback=None): if comments: file.write("\n//--Lamps--\n\n") + for ob in bpy.data.objects: + if ob.type == 'MESH': + for mod in ob.modifiers: + if mod.type == 'BOOLEAN': + if mod.object not in csg_list: + csg_list.append(mod.object) + if csg_list != []: + csg = False + sel = no_renderable_objects(scene) + exportMeshes(scene, sel, csg) + + csg = True + sel = renderable_objects(scene) + exportLamps([L for L in sel if (L.type == 'LAMP' and L.pov.object_as != 'RAINBOW')]) if comments: @@ -3655,7 +3719,7 @@ def write_pov(filename, scene=None, info_callback=None): if comments: file.write("//--Mesh objects--\n") - exportMeshes(scene, sel) + exportMeshes(scene, sel, csg) #What follow used to happen here: #exportCamera() @@ -3745,7 +3809,7 @@ def write_pov_ini(scene, filename_ini, filename_log, filename_pov, filename_imag class PovrayRender(bpy.types.RenderEngine): bl_idname = 'POVRAY_RENDER' - bl_label = "POV-Ray 3.7" + bl_label = "Persitence Of Vision" DELAY = 0.5 @staticmethod @@ -3951,8 +4015,8 @@ class PovrayRender(bpy.types.RenderEngine): self._temp_file_in = os.path.join(preview_dir, povPath) self._temp_file_ini = os.path.join(preview_dir, (os.path.splitext(self._temp_file_in)[0]+".INI")) self._temp_file_log = os.path.join(preview_dir, "alltext.out") - - + + ''' try: os.remove(self._temp_file_in) # so as not to load the old file @@ -4000,7 +4064,14 @@ class PovrayRender(bpy.types.RenderEngine): # Start Rendering! try: - _process = subprocess.Popen([pov_binary, self._temp_file_ini] + extra_args, + if sys.platform[:3] != "win" and scene.pov.sdl_window_enable: #segfault on linux == False !!! + env = {'POV_DISPLAY_SCALED': 'off'} + env.update(os.environ) + self._process = subprocess.Popen([pov_binary, self._temp_file_ini], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + env=env) + else: + self._process = subprocess.Popen([pov_binary, self._temp_file_ini] + extra_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) except OSError: # TODO, report api @@ -4015,7 +4086,7 @@ class PovrayRender(bpy.types.RenderEngine): print("Command line arguments passed: " + str(extra_args)) #return True self.update_stats("", "POV-Ray 3.7: Parsing File") - + # Indented in main function now so repeated here but still not working @@ -4040,7 +4111,7 @@ class PovrayRender(bpy.types.RenderEngine): print(f.read()) self.update_stats("", "") - + if scene.pov.tempfiles_enable or scene.pov.deletefiles_enable: self._cleanup() else: @@ -4394,7 +4465,7 @@ class RunPovTextRender(Operator): bpy.ops.render.render() - + #empty text name property engain scene.pov.text_block = "" - return {'FINISHED'}
\ No newline at end of file + return {'FINISHED'}
\ No newline at end of file diff --git a/render_povray/shading.py b/render_povray/shading.py index 5a371707..03a63a9d 100644 --- a/render_povray/shading.py +++ b/render_povray/shading.py @@ -245,13 +245,11 @@ def writeMaterial(using_uberpov, DEF_MAT_NAME, scene, tabWrite, safety, comments for t in material.texture_slots: if t and t.use and t.texture is not None: if (t.texture.type == 'IMAGE' and t.texture.image) or t.texture.type != 'IMAGE': - validPath=True - else: - validPath=False - if(t and t.use and validPath and - (t.use_map_specular or t.use_map_raymir or t.use_map_normal or t.use_map_alpha)): - special_texture_found = True - continue # Some texture found + #validPath + if(t and t.use and + (t.use_map_specular or t.use_map_raymir or t.use_map_normal or t.use_map_alpha)): + special_texture_found = True + continue # Some texture found if special_texture_found or colored_specular_found: # Level=1 Means No specular nor Mirror reflection @@ -1087,7 +1085,6 @@ def writeTextureInfluence(mater, materialNames, LocalMaterialNames, path_image, else: if texturesDif and texturesDif.startswith("PAT_"): tabWrite("pigment{%s %s}\n" %(texturesDif, mappingDif)) - print('XXXMEEEERDE!') else: tabWrite("pigment {\n") tabWrite("uv_mapping image_map {\n") diff --git a/render_povray/ui.py b/render_povray/ui.py index ca6a5db2..b4a08292 100644 --- a/render_povray/ui.py +++ b/render_povray/ui.py @@ -22,6 +22,7 @@ import bpy import sys #really import here and in render.py? import os #really import here and in render.py? from os.path import isfile +from bl_operators.presets import AddPresetBase # Use some of the existing buttons. from bl_ui import properties_render @@ -41,6 +42,61 @@ properties_world.WORLD_PT_world.COMPAT_ENGINES.add('POVRAY_RENDER') properties_world.WORLD_PT_mist.COMPAT_ENGINES.add('POVRAY_RENDER') del properties_world +class POV_WORLD_MT_presets(bpy.types.Menu): + bl_label = "World Presets" + preset_subdir = "pov/world" + preset_operator = "script.execute_preset" + draw = bpy.types.Menu.draw_preset + + +class AddPresetWorld(AddPresetBase, bpy.types.Operator): + '''Add a World Preset''' + bl_idname = "object.world_preset_add" + bl_label = "Add World Preset" + preset_menu = "POV_WORLD_MT_presets" + + # variable used for all preset values + preset_defines = [ + "scene = bpy.context.scene" + ] + + # properties to store in the preset + preset_values = [ + "scene.world.use_sky_blend", + "scene.world.horizon_color", + "scene.world.zenith_color", + "scene.world.ambient_color", + "scene.world.mist_settings.use_mist", + "scene.world.mist_settings.intensity", + "scene.world.mist_settings.depth", + "scene.world.mist_settings.start", + "scene.pov.media_enable", + "scene.pov.media_scattering_type", + "scene.pov.media_samples", + "scene.pov.media_diffusion_scale", + "scene.pov.media_diffusion_color", + "scene.pov.media_absorption_scale", + "scene.pov.media_absorption_color", + "scene.pov.media_eccentricity", + ] + + # where to store the preset + preset_subdir = "pov/world" + +# Draw into an existing panel +def world_panel_func(self, context): + layout = self.layout + + row = layout.row(align=True) + row.menu(POV_WORLD_MT_presets.__name__, text=POV_WORLD_MT_presets.bl_label) + row.operator(AddPresetWorld.bl_idname, text="", icon='ZOOMIN') + row.operator(AddPresetWorld.bl_idname, text="", icon='ZOOMOUT').remove_active = True + + +classes = ( + POV_WORLD_MT_presets, + AddPresetWorld, + ) # Example of wrapping every class 'as is' from bl_ui import properties_texture @@ -450,6 +506,52 @@ class LAMP_PT_POV_lamp(PovLampButtonsPanel, bpy.types.Panel): draw = properties_data_lamp.DATA_PT_lamp.draw +class POV_LAMP_MT_presets(bpy.types.Menu): + bl_label = "Lamp Presets" + preset_subdir = "pov/lamp" + preset_operator = "script.execute_preset" + draw = bpy.types.Menu.draw_preset + + +class AddPresetLamp(AddPresetBase, bpy.types.Operator): + '''Add a Lamp Preset''' + bl_idname = "object.lamp_preset_add" + bl_label = "Add Lamp Preset" + preset_menu = "POV_LAMP_MT_presets" + + # variable used for all preset values + preset_defines = [ + "lampdata = bpy.context.object.data" + ] + + # properties to store in the preset + preset_values = [ + "lampdata.type", + "lampdata.color", + ] + + # where to store the preset + preset_subdir = "pov/lamp" + + + + + +# Draw into an existing panel +def lamp_panel_func(self, context): + layout = self.layout + + row = layout.row(align=True) + row.menu(POV_LAMP_MT_presets.__name__, text=POV_LAMP_MT_presets.bl_label) + row.operator(AddPresetLamp.bl_idname, text="", icon='ZOOMIN') + row.operator(AddPresetLamp.bl_idname, text="", icon='ZOOMOUT').remove_active = True + + +classes = ( + POV_LAMP_MT_presets, + AddPresetLamp, + ) + class LAMP_PT_POV_sunsky(PovLampButtonsPanel, bpy.types.Panel): bl_label = properties_data_lamp.DATA_PT_sunsky.bl_label @@ -606,7 +708,10 @@ class RENDER_PT_povray_render_settings(RenderButtonsPanel, bpy.types.Panel): scene = context.scene #layout.active = (scene.pov.max_trace_level != 0) - + + if sys.platform[:3] != "win": + layout.prop(scene.pov, "sdl_window_enable", text="POV-Ray SDL Window") + col = layout.column() col.label(text="Global Settings:") @@ -784,7 +889,71 @@ class RENDER_PT_povray_radiosity(RenderButtonsPanel, bpy.types.Panel): col.prop(scene.pov, "radio_subsurface") - + + + +class POV_RADIOSITY_MT_presets(bpy.types.Menu): + bl_label = "Radiosity Presets" + preset_subdir = "pov/radiosity" + preset_operator = "script.execute_preset" + draw = bpy.types.Menu.draw_preset + + +class AddPresetRadiosity(AddPresetBase, bpy.types.Operator): + '''Add a Radiosity Preset''' + bl_idname = "scene.radiosity_preset_add" + bl_label = "Add Radiosity Preset" + preset_menu = "POV_RADIOSITY_MT_presets" + + # variable used for all preset values + preset_defines = [ + "scene = bpy.context.scene" + ] + + # properties to store in the preset + preset_values = [ + "scene.pov.radio_display_advanced", + "scene.pov.radio_adc_bailout", + "scene.pov.radio_always_sample", + "scene.pov.radio_brightness", + "scene.pov.radio_count", + "scene.pov.radio_error_bound", + "scene.pov.radio_gray_threshold", + "scene.pov.radio_low_error_factor", + "scene.pov.radio_media", + "scene.pov.radio_subsurface", + "scene.pov.radio_minimum_reuse", + "scene.pov.radio_maximum_reuse", + "scene.pov.radio_nearest_count", + "scene.pov.radio_normal", + "scene.pov.radio_recursion_limit", + "scene.pov.radio_pretrace_start", + "scene.pov.radio_pretrace_end", + ] + + # where to store the preset + preset_subdir = "pov/radiosity" + + + + + +# Draw into an existing panel +def rad_panel_func(self, context): + layout = self.layout + + row = layout.row(align=True) + row.menu(POV_RADIOSITY_MT_presets.__name__, text=POV_RADIOSITY_MT_presets.bl_label) + row.operator(AddPresetRadiosity.bl_idname, text="", icon='ZOOMIN') + row.operator(AddPresetRadiosity.bl_idname, text="", icon='ZOOMOUT').remove_active = True + + +classes = ( + POV_RADIOSITY_MT_presets, + AddPresetRadiosity, + ) + + class RENDER_PT_povray_media(WorldButtonsPanel, bpy.types.Panel): bl_label = "Atmosphere Media" COMPAT_ENGINES = {'POVRAY_RENDER'} @@ -801,10 +970,22 @@ class RENDER_PT_povray_media(WorldButtonsPanel, bpy.types.Panel): layout.active = scene.pov.media_enable - row = layout.row() - row.prop(scene.pov, "media_samples", text="Samples") - row.prop(scene.pov, "media_color", text="") - + col = layout.column() + col.prop(scene.pov, "media_scattering_type", text="") + col = layout.column() + col.prop(scene.pov, "media_samples", text="Samples") + split = layout.split() + col = split.column(align=True) + col.label(text="Scattering:") + col.prop(scene.pov, "media_diffusion_scale") + col.prop(scene.pov, "media_diffusion_color", text="") + col = split.column(align=True) + col.label(text="Absorption:") + col.prop(scene.pov, "media_absorption_scale") + col.prop(scene.pov, "media_absorption_color", text="") + if scene.pov.media_scattering_type == '5': + col = layout.column() + col.prop(scene.pov, "media_eccentricity", text="Eccentricity") ##class RENDER_PT_povray_baking(RenderButtonsPanel, bpy.types.Panel): ## bl_label = "Baking" ## COMPAT_ENGINES = {'POVRAY_RENDER'} @@ -821,7 +1002,7 @@ class RENDER_PT_povray_media(WorldButtonsPanel, bpy.types.Panel): ## rd = scene.render ## ## layout.active = scene.pov.baking_enable -'''XXX WIP preparing for CSG + class MODIFIERS_PT_povray_modifiers(ModifierButtonsPanel, bpy.types.Panel): bl_label = "POV-Ray" COMPAT_ENGINES = {'POVRAY_RENDER'} @@ -850,7 +1031,7 @@ class MODIFIERS_PT_povray_modifiers(ModifierButtonsPanel, bpy.types.Panel): col = layout.column() # Inside Vector for CSG col.prop(ob.pov, "inside_vector") -''' + class MATERIAL_PT_povray_activate_node(MaterialButtonsPanel, bpy.types.Panel): bl_label = "Activate Node Settings" diff --git a/space_view3d_stored_views/__init__.py b/space_view3d_stored_views/__init__.py index 8deca8f0..a9767bff 100644 --- a/space_view3d_stored_views/__init__.py +++ b/space_view3d_stored_views/__init__.py @@ -20,13 +20,14 @@ bl_info = { "name": "Stored Views", "description": "Save and restore User defined views, pov, layers and display configs", "author": "nfloyd, Francesco Siddi", - "version": (0, 3, 6), + "version": (0, 3, 7), "blender": (2, 7, 8), "location": "View3D > Properties > Stored Views", "warning": "", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.5/" "Py/Scripts/3D_interaction/stored_views", - "category": "3D View"} + "category": "3D View" +} """ ACKNOWLEDGMENT @@ -65,14 +66,14 @@ else: import bpy from bpy.props import ( - BoolProperty, - IntProperty, - PointerProperty, - ) + BoolProperty, + IntProperty, + PointerProperty, +) from bpy.types import ( - AddonPreferences, - Operator, - ) + AddonPreferences, + Operator, +) class VIEW3D_stored_views_initialize(Operator): @@ -99,20 +100,20 @@ class VIEW3D_stored_views_preferences(AddonPreferences): bl_idname = __name__ show_exporters = BoolProperty( - name="Enable I/O Operators", - default=False, - description="Enable Import/Export Operations in the UI:\n" - "Import Stored Views preset,\n" - "Export Stored Views preset and \n" - "Import stored views from scene", - ) + name="Enable I/O Operators", + default=False, + description="Enable Import/Export Operations in the UI:\n" + "Import Stored Views preset,\n" + "Export Stored Views preset and \n" + "Import stored views from scene", + ) view_3d_update_rate = IntProperty( - name="3D view update", - description="Update rate of the 3D view redraw\n" - "Increse the value if the UI feels sluggish", - min=1, max=10, - default=1 - ) + name="3D view update", + description="Update rate of the 3D view redraw\n" + "Increse the value if the UI feels sluggish", + min=1, max=10, + default=1 + ) def draw(self, context): layout = self.layout diff --git a/space_view3d_stored_views/io.py b/space_view3d_stored_views/io.py index b6aec4dd..e9c325ff 100644 --- a/space_view3d_stored_views/io.py +++ b/space_view3d_stored_views/io.py @@ -8,13 +8,13 @@ import shutil import bpy from bpy.types import Operator from bpy.props import ( - BoolProperty, - StringProperty - ) + BoolProperty, + StringProperty, +) from bpy_extras.io_utils import ( - ExportHelper, - ImportHelper, - ) + ExportHelper, + ImportHelper, +) from . import bl_info from .core import get_preferences from .operators import DataStore @@ -162,11 +162,12 @@ class IO_Utils(): return False # io_filters = sv.settings.io_filters - sv_data = {"point_of_views": sv.pov_list, - "views": sv.view_list, - "layers": sv.layers_list, - "displays": sv.display_list} - + sv_data = { + "point_of_views": sv.pov_list, + "views": sv.view_list, + "layers": sv.layers_list, + "displays": sv.display_list + } for sv_struct, props in dump["data"].items(): """ is_filtered = getattr(io_filters, sv_struct) @@ -203,20 +204,20 @@ class IO_Utils(): class VIEW3D_stored_views_import(Operator, ImportHelper): - bl_idname = "stored_views.import" + bl_idname = "stored_views.import_blsv" bl_label = "Import Stored Views preset" bl_description = "Import a .blsv preset file to the current Stored Views" filename_ext = ".blsv" filter_glob = StringProperty( - default="*.blsv", - options={'HIDDEN'} - ) + default="*.blsv", + options={'HIDDEN'} + ) replace = BoolProperty( - name="Replace", - default=True, - description="Replace current stored views, otherwise append" - ) + name="Replace", + default=True, + description="Replace current stored views, otherwise append" + ) @classmethod def poll(cls, context): @@ -227,7 +228,7 @@ class VIEW3D_stored_views_import(Operator, ImportHelper): exists = os.path.isfile(self.filepath) if self.filepath else False if not exists: self.report({'WARNING'}, - "No filepath specified, or file could not be found. Operation Cancelled") + "No filepath specified or file could not be found. Operation Cancelled") return {'CANCELLED'} # apply chosen preset @@ -258,15 +259,15 @@ class VIEW3D_stored_views_import_from_scene(Operator): bl_description = "Import currently stored views from an another scene" scene_name = StringProperty( - name="Scene Name", - description="A current blend scene", - default="" - ) + name="Scene Name", + description="A current blend scene", + default="" + ) replace = BoolProperty( - name="Replace", - default=True, - description="Replace current stored views, otherwise append" - ) + name="Replace", + default=True, + description="Replace current stored views, otherwise append" + ) @classmethod def poll(cls, context): @@ -300,23 +301,23 @@ class VIEW3D_stored_views_import_from_scene(Operator): class VIEW3D_stored_views_export(Operator, ExportHelper): - bl_idname = "stored_views.export" + bl_idname = "stored_views.export_blsv" bl_label = "Export Stored Views preset" bl_description = "Export the current Stored Views to a .blsv preset file" filename_ext = ".blsv" filepath = StringProperty( - default=os.path.join(IO_Utils.get_preset_path()[0], "untitled") - ) + default=os.path.join(IO_Utils.get_preset_path()[0], "untitled") + ) filter_glob = StringProperty( - default="*.blsv", - options={'HIDDEN'} - ) + default="*.blsv", + options={'HIDDEN'} + ) preset_name = StringProperty( - name="Preset name", - default="", - description="Name of the stored views preset" - ) + name="Preset name", + default="", + description="Name of the stored views preset" + ) @classmethod def poll(cls, context): @@ -324,4 +325,5 @@ class VIEW3D_stored_views_export(Operator, ExportHelper): def execute(self, context): IO_Utils.stored_views_export_to_blsv(self.filepath, self.preset_name) + return{'FINISHED'} diff --git a/space_view3d_stored_views/ui.py b/space_view3d_stored_views/ui.py index 17d8c729..5025db19 100644 --- a/space_view3d_stored_views/ui.py +++ b/space_view3d_stored_views/ui.py @@ -7,9 +7,9 @@ import bpy import blf from . import core from bpy.types import ( - Operator, - Panel, - ) + Operator, + Panel, +) """ If view name display is enabled, @@ -185,8 +185,8 @@ class VIEW3D_PT_properties_stored_views(Panel): if core.get_preferences(): row = layout.row(align=True) row.operator("stored_views.import_from_scene", text="Import from Scene") - row.operator("stored_views.import", text="", icon="IMPORT") - row.operator("stored_views.export", text="", icon="EXPORT") + row.operator("stored_views.import_blsv", text="", icon="IMPORT") + row.operator("stored_views.export_blsv", text="", icon="EXPORT") data_store = core.DataStore() list = data_store.list diff --git a/ui_layer_manager.py b/ui_layer_manager.py index b354b1ec..018aad26 100644 --- a/ui_layer_manager.py +++ b/ui_layer_manager.py @@ -364,7 +364,7 @@ class SCENE_OT_namedlayer_select_objects_by_layer(Operator): for obj in objects: obj.select = False else: - bpy.ops.object.select_by_layer(extend=self.extend, layers=layer_idx + 1) + bpy.ops.object.select_by_layer(match='SHARED', extend=self.extend, layers=layer_idx + 1) return {'FINISHED'} diff --git a/uv_magic_uv/__init__.py b/uv_magic_uv/__init__.py index 171a5ac4..080d2414 100644 --- a/uv_magic_uv/__init__.py +++ b/uv_magic_uv/__init__.py @@ -20,18 +20,18 @@ __author__ = "Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" +__version__ = "5.1" +__date__ = "24 Feb 2018" bl_info = { "name": "Magic UV", - "author": "Nutti, Mifth, Jace Priester, kgeogeo, mem, " + "author": "Nutti, Mifth, Jace Priester, kgeogeo, mem, imdjs" "Keith (Wahooney) Boshoff, McBuff, MaxRobinot, Alexander Milovsky", - "version": (4, 5, 0), + "version": (5, 1, 0), "blender": (2, 79, 0), "location": "See Add-ons Preferences", - "description": "UV Manipulator Tools. See Add-ons Preferences for details", + "description": "UV Toolset. See Add-ons Preferences for details", "warning": "", "support": "COMMUNITY", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/" @@ -42,98 +42,29 @@ bl_info = { if "bpy" in locals(): import importlib - importlib.reload(muv_preferences) - importlib.reload(muv_menu) - importlib.reload(muv_common) - importlib.reload(muv_props) - importlib.reload(muv_cpuv_ops) - importlib.reload(muv_cpuv_selseq_ops) - importlib.reload(muv_fliprot_ops) - importlib.reload(muv_transuv_ops) - importlib.reload(muv_uvbb_ops) - importlib.reload(muv_mvuv_ops) - importlib.reload(muv_texproj_ops) - importlib.reload(muv_packuv_ops) - importlib.reload(muv_texlock_ops) - importlib.reload(muv_mirroruv_ops) - importlib.reload(muv_wsuv_ops) - importlib.reload(muv_unwrapconst_ops) - importlib.reload(muv_preserve_uv_aspect) - importlib.reload(muv_uvw_ops) + importlib.reload(op) + importlib.reload(ui) + importlib.reload(common) + importlib.reload(preferences) + importlib.reload(properites) else: - from . import muv_preferences - from . import muv_menu - from . import muv_common - from . import muv_props - from . import muv_cpuv_ops - from . import muv_cpuv_selseq_ops - from . import muv_fliprot_ops - from . import muv_transuv_ops - from . import muv_uvbb_ops - from . import muv_mvuv_ops - from . import muv_texproj_ops - from . import muv_packuv_ops - from . import muv_texlock_ops - from . import muv_mirroruv_ops - from . import muv_wsuv_ops - from . import muv_unwrapconst_ops - from . import muv_preserve_uv_aspect - from . import muv_uvw_ops + from . import op + from . import ui + from . import common + from . import preferences + from . import properites import bpy -def view3d_uvmap_menu_fn(self, context): - self.layout.separator() - self.layout.menu(muv_menu.MUV_CPUVMenu.bl_idname, icon="IMAGE_COL") - self.layout.operator( - muv_fliprot_ops.MUV_FlipRot.bl_idname, icon="IMAGE_COL") - self.layout.menu(muv_menu.MUV_TransUVMenu.bl_idname, icon="IMAGE_COL") - self.layout.operator(muv_mvuv_ops.MUV_MVUV.bl_idname, icon="IMAGE_COL") - self.layout.menu(muv_menu.MUV_TexLockMenu.bl_idname, icon="IMAGE_COL") - self.layout.operator( - muv_mirroruv_ops.MUV_MirrorUV.bl_idname, icon="IMAGE_COL") - self.layout.menu(muv_menu.MUV_WSUVMenu.bl_idname, icon="IMAGE_COL") - self.layout.operator( - muv_unwrapconst_ops.MUV_UnwrapConstraint.bl_idname, icon='IMAGE_COL') - self.layout.menu( - muv_preserve_uv_aspect.MUV_PreserveUVAspectMenu.bl_idname, - icon='IMAGE_COL') - self.layout.menu(muv_menu.MUV_UVWMenu.bl_idname, icon="IMAGE_COL") - - -def image_uvs_menu_fn(self, context): - self.layout.separator() - self.layout.operator(muv_packuv_ops.MUV_PackUV.bl_idname, icon="IMAGE_COL") - - -def view3d_object_menu_fn(self, context): - self.layout.separator() - self.layout.menu(muv_menu.MUV_CPUVObjMenu.bl_idname, icon="IMAGE_COL") - - def register(): bpy.utils.register_module(__name__) - bpy.types.VIEW3D_MT_uv_map.append(view3d_uvmap_menu_fn) - bpy.types.IMAGE_MT_uvs.append(image_uvs_menu_fn) - bpy.types.VIEW3D_MT_object.append(view3d_object_menu_fn) - try: - bpy.types.VIEW3D_MT_Object.append(view3d_object_menu_fn) - except: - pass - muv_props.init_props(bpy.types.Scene) + properites.init_props(bpy.types.Scene) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.VIEW3D_MT_uv_map.remove(view3d_uvmap_menu_fn) - bpy.types.IMAGE_MT_uvs.remove(image_uvs_menu_fn) - bpy.types.VIEW3D_MT_object.remove(view3d_object_menu_fn) - try: - bpy.types.VIEW3D_MT_Object.remove(view3d_object_menu_fn) - except: - pass - muv_props.clear_props(bpy.types.Scene) + properites.clear_props(bpy.types.Scene) if __name__ == "__main__": diff --git a/uv_magic_uv/common.py b/uv_magic_uv/common.py new file mode 100644 index 00000000..6d3d9df7 --- /dev/null +++ b/uv_magic_uv/common.py @@ -0,0 +1,604 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +from collections import defaultdict +from pprint import pprint +from math import fabs, sqrt + +import bpy +from mathutils import Vector +import bmesh + + +DEBUG = False + + +def debug_print(*s): + """ + Print message to console in debugging mode + """ + + if DEBUG: + pprint(s) + + +def check_version(major, minor, _): + """ + Check blender version + """ + + if bpy.app.version[0] == major and bpy.app.version[1] == minor: + return 0 + if bpy.app.version[0] > major: + return 1 + if bpy.app.version[1] > minor: + return 1 + return -1 + + +def redraw_all_areas(): + """ + Redraw all areas + """ + + for area in bpy.context.screen.areas: + area.tag_redraw() + + +def get_space(area_type, region_type, space_type): + """ + Get current area/region/space + """ + + area = None + region = None + space = None + + for area in bpy.context.screen.areas: + if area.type == area_type: + break + else: + return (None, None, None) + for region in area.regions: + if region.type == region_type: + break + for space in area.spaces: + if space.type == space_type: + break + + return (area, region, space) + + +def __get_island_info(uv_layer, islands): + """ + get information about each island + """ + + island_info = [] + for isl in islands: + info = {} + max_uv = Vector((-10000000.0, -10000000.0)) + min_uv = Vector((10000000.0, 10000000.0)) + ave_uv = Vector((0.0, 0.0)) + num_uv = 0 + for face in isl: + n = 0 + a = Vector((0.0, 0.0)) + ma = Vector((-10000000.0, -10000000.0)) + mi = Vector((10000000.0, 10000000.0)) + for l in face['face'].loops: + uv = l[uv_layer].uv + ma.x = max(uv.x, ma.x) + ma.y = max(uv.y, ma.y) + mi.x = min(uv.x, mi.x) + mi.y = min(uv.y, mi.y) + a = a + uv + n = n + 1 + ave_uv = ave_uv + a + num_uv = num_uv + n + a = a / n + max_uv.x = max(ma.x, max_uv.x) + max_uv.y = max(ma.y, max_uv.y) + min_uv.x = min(mi.x, min_uv.x) + min_uv.y = min(mi.y, min_uv.y) + face['max_uv'] = ma + face['min_uv'] = mi + face['ave_uv'] = a + ave_uv = ave_uv / num_uv + + info['center'] = ave_uv + info['size'] = max_uv - min_uv + info['num_uv'] = num_uv + info['group'] = -1 + info['faces'] = isl + info['max'] = max_uv + info['min'] = min_uv + + island_info.append(info) + + return island_info + + +def __parse_island(bm, face_idx, faces_left, island, + face_to_verts, vert_to_faces): + """ + Parse island + """ + + if face_idx in faces_left: + faces_left.remove(face_idx) + island.append({'face': bm.faces[face_idx]}) + for v in face_to_verts[face_idx]: + connected_faces = vert_to_faces[v] + if connected_faces: + for cf in connected_faces: + __parse_island(bm, cf, faces_left, island, face_to_verts, + vert_to_faces) + + +def __get_island(bm, face_to_verts, vert_to_faces): + """ + Get island list + """ + + uv_island_lists = [] + faces_left = set(face_to_verts.keys()) + while faces_left: + current_island = [] + face_idx = list(faces_left)[0] + __parse_island(bm, face_idx, faces_left, current_island, + face_to_verts, vert_to_faces) + uv_island_lists.append(current_island) + + return uv_island_lists + + +def __create_vert_face_db(faces, uv_layer): + # create mesh database for all faces + face_to_verts = defaultdict(set) + vert_to_faces = defaultdict(set) + for f in faces: + for l in f.loops: + id_ = l[uv_layer].uv.to_tuple(5), l.vert.index + face_to_verts[f.index].add(id_) + vert_to_faces[id_].add(f.index) + + return (face_to_verts, vert_to_faces) + + +def get_island_info(obj, only_selected=True): + bm = bmesh.from_edit_mesh(obj.data) + if check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + return get_island_info_from_bmesh(bm, only_selected) + + +def get_island_info_from_bmesh(bm, only_selected=True): + if not bm.loops.layers.uv: + return None + uv_layer = bm.loops.layers.uv.verify() + + # create database + if only_selected: + selected_faces = [f for f in bm.faces if f.select] + else: + selected_faces = [f for f in bm.faces] + + return get_island_info_from_faces(bm, selected_faces, uv_layer) + + +def get_island_info_from_faces(bm, faces, uv_layer): + ftv, vtf = __create_vert_face_db(faces, uv_layer) + + # Get island information + uv_island_lists = __get_island(bm, ftv, vtf) + island_info = __get_island_info(uv_layer, uv_island_lists) + + return island_info + + +def get_uvimg_editor_board_size(area): + if area.spaces.active.image: + return area.spaces.active.image.size + + return (255.0, 255.0) + + +def calc_polygon_2d_area(points): + area = 0.0 + for i, p1 in enumerate(points): + p2 = points[(i + 1) % len(points)] + v1 = p1 - points[0] + v2 = p2 - points[0] + a = v1.x * v2.y - v1.y * v2.x + area = area + a + + return fabs(0.5 * area) + + +def calc_polygon_3d_area(points): + area = 0.0 + for i, p1 in enumerate(points): + p2 = points[(i + 1) % len(points)] + v1 = p1 - points[0] + v2 = p2 - points[0] + cx = v1.y * v2.z - v1.z * v2.y + cy = v1.z * v2.x - v1.x * v2.z + cz = v1.x * v2.y - v1.y * v2.x + a = sqrt(cx * cx + cy * cy + cz * cz) + area = area + a + + return 0.5 * area + + +def measure_mesh_area(obj): + bm = bmesh.from_edit_mesh(obj.data) + if check_version(2, 73, 0) >= 0: + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + bm.faces.ensure_lookup_table() + + sel_faces = [f for f in bm.faces if f.select] + + # measure + mesh_area = 0.0 + for f in sel_faces: + verts = [l.vert.co for l in f.loops] + f_mesh_area = calc_polygon_3d_area(verts) + mesh_area = mesh_area + f_mesh_area + + return mesh_area + + +def measure_uv_area(obj): + bm = bmesh.from_edit_mesh(obj.data) + if check_version(2, 73, 0) >= 0: + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + bm.faces.ensure_lookup_table() + + if not bm.loops.layers.uv: + return None + uv_layer = bm.loops.layers.uv.verify() + + if not bm.faces.layers.tex: + return None + tex_layer = bm.faces.layers.tex.verify() + + sel_faces = [f for f in bm.faces if f.select] + + # measure + uv_area = 0.0 + for f in sel_faces: + uvs = [l[uv_layer].uv for l in f.loops] + f_uv_area = calc_polygon_2d_area(uvs) + + if not tex_layer: + return None + img = f[tex_layer].image + # not found, try to search from node + if not img: + for mat in obj.material_slots: + for node in mat.material.node_tree.nodes: + tex_node_types = [ + 'TEX_ENVIRONMENT', + 'TEX_IMAGE', + ] + if (node.type in tex_node_types) and node.image: + img = node.image + if not img: + return None + uv_area = uv_area + f_uv_area * img.size[0] * img.size[1] + + return uv_area + + +def diff_point_to_segment(a, b, p): + ab = b - a + normal_ab = ab.normalized() + + ap = p - a + dist_ax = normal_ab.dot(ap) + + # cross point + x = a + normal_ab * dist_ax + + # difference between cross point and point + xp = p - x + + return xp, x + + +# get selected loop pair whose loops are connected each other +def __get_loop_pairs(l, uv_layer): + + def __get_loop_pairs_internal(l_, pairs_, uv_layer_, parsed_): + parsed_.append(l_) + for ll in l_.vert.link_loops: + # forward direction + lln = ll.link_loop_next + # if there is same pair, skip it + found = False + for p in pairs_: + if (ll in p) and (lln in p): + found = True + break + # two loops must be selected + if ll[uv_layer_].select and lln[uv_layer_].select: + if not found: + pairs_.append([ll, lln]) + if lln not in parsed_: + __get_loop_pairs_internal(lln, pairs_, uv_layer_, parsed_) + + # backward direction + llp = ll.link_loop_prev + # if there is same pair, skip it + found = False + for p in pairs_: + if (ll in p) and (llp in p): + found = True + break + # two loops must be selected + if ll[uv_layer_].select and llp[uv_layer_].select: + if not found: + pairs_.append([ll, llp]) + if llp not in parsed_: + __get_loop_pairs_internal(llp, pairs_, uv_layer_, parsed_) + + pairs = [] + parsed = [] + __get_loop_pairs_internal(l, pairs, uv_layer, parsed) + + return pairs + + +# sort pair by vertex +# (v0, v1) - (v1, v2) - (v2, v3) .... +def __sort_loop_pairs(uv_layer, pairs, closed): + rest = pairs + sorted_pairs = [rest[0]] + rest.remove(rest[0]) + + # prepend + while True: + p1 = sorted_pairs[0] + for p2 in rest: + if p1[0].vert == p2[0].vert: + sorted_pairs.insert(0, [p2[1], p2[0]]) + rest.remove(p2) + break + elif p1[0].vert == p2[1].vert: + sorted_pairs.insert(0, [p2[0], p2[1]]) + rest.remove(p2) + break + else: + break + + # append + while True: + p1 = sorted_pairs[-1] + for p2 in rest: + if p1[1].vert == p2[0].vert: + sorted_pairs.append([p2[0], p2[1]]) + rest.remove(p2) + break + elif p1[1].vert == p2[1].vert: + sorted_pairs.append([p2[1], p2[0]]) + rest.remove(p2) + break + else: + break + + begin_vert = sorted_pairs[0][0].vert + end_vert = sorted_pairs[-1][-1].vert + if begin_vert != end_vert: + return sorted_pairs, "" + if closed and (begin_vert == end_vert): + # if the sequence of UV is circular, it is ok + return sorted_pairs, "" + + # if the begin vertex and the end vertex are same, search the UVs which + # are separated each other + tmp_pairs = sorted_pairs + for i, (p1, p2) in enumerate(zip(tmp_pairs[:-1], tmp_pairs[1:])): + diff = p2[0][uv_layer].uv - p1[-1][uv_layer].uv + if diff.length > 0.000000001: + # UVs are separated + sorted_pairs = tmp_pairs[i + 1:] + sorted_pairs.extend(tmp_pairs[:i + 1]) + break + else: + p1 = tmp_pairs[0] + p2 = tmp_pairs[-1] + diff = p2[-1][uv_layer].uv - p1[0][uv_layer].uv + if diff.length < 0.000000001: + # all UVs are not separated + return None, "All UVs are not separted" + + return sorted_pairs, "" + + +# get index of the island group which includes loop +def __get_island_group_include_loop(loop, island_info): + for i, isl in enumerate(island_info): + for f in isl['faces']: + for l in f['face'].loops: + if l == loop: + return i # found + + return -1 # not found + + +# get index of the island group which includes pair. +# if island group is not same between loops, it will be invalid +def __get_island_group_include_pair(pair, island_info): + l1_grp = __get_island_group_include_loop(pair[0], island_info) + if l1_grp == -1: + return -1 # not found + + for p in pair[1:]: + l2_grp = __get_island_group_include_loop(p, island_info) + if (l2_grp == -1) or (l1_grp != l2_grp): + return -1 # not found or invalid + + return l1_grp + + +# x ---- x <- next_loop_pair +# | | +# o ---- o <- pair +def __get_next_loop_pair(pair): + lp = pair[0].link_loop_prev + if lp.vert == pair[1].vert: + lp = pair[0].link_loop_next + if lp.vert == pair[1].vert: + # no loop is found + return None + + ln = pair[1].link_loop_next + if ln.vert == pair[0].vert: + ln = pair[1].link_loop_prev + if ln.vert == pair[0].vert: + # no loop is found + return None + + # tri-face + if lp == ln: + return [lp] + + # quad-face + return [lp, ln] + + +# | ---- | +# % ---- % <- next_poly_loop_pair +# x ---- x <- next_loop_pair +# | | +# o ---- o <- pair +def __get_next_poly_loop_pair(pair): + v1 = pair[0].vert + v2 = pair[1].vert + for l1 in v1.link_loops: + if l1 == pair[0]: + continue + for l2 in v2.link_loops: + if l2 == pair[1]: + continue + if l1.link_loop_next == l2: + return [l1, l2] + elif l1.link_loop_prev == l2: + return [l1, l2] + + # no next poly loop is found + return None + + +# get loop sequence in the same island +def __get_loop_sequence_internal(uv_layer, pairs, island_info, closed): + loop_sequences = [] + for pair in pairs: + seqs = [pair] + p = pair + isl_grp = __get_island_group_include_pair(pair, island_info) + if isl_grp == -1: + return None, "Can not find the island or invalid island" + + while True: + nlp = __get_next_loop_pair(p) + if not nlp: + break # no more loop pair + nlp_isl_grp = __get_island_group_include_pair(nlp, island_info) + if nlp_isl_grp != isl_grp: + break # another island + for nlpl in nlp: + if nlpl[uv_layer].select: + return None, "Do not select UV which does not belong to " \ + "the end edge" + + seqs.append(nlp) + + # when face is triangle, it indicates CLOSED + if (len(nlp) == 1) and closed: + break + + nplp = __get_next_poly_loop_pair(nlp) + if not nplp: + break # no more loop pair + nplp_isl_grp = __get_island_group_include_pair(nplp, island_info) + if nplp_isl_grp != isl_grp: + break # another island + + # check if the UVs are already parsed. + # this check is needed for the mesh which has the circular + # sequence of the verticies + matched = False + for p1 in seqs: + p2 = nplp + if ((p1[0] == p2[0]) and (p1[1] == p2[1])) or \ + ((p1[0] == p2[1]) and (p1[1] == p2[0])): + matched = True + if matched: + debug_print("This is a circular sequence") + break + + for nlpl in nplp: + if nlpl[uv_layer].select: + return None, "Do not select UV which does not belong to " \ + "the end edge" + + seqs.append(nplp) + + p = nplp + + loop_sequences.append(seqs) + return loop_sequences, "" + + +def get_loop_sequences(bm, uv_layer, closed=False): + sel_faces = [f for f in bm.faces if f.select] + + # get candidate loops + cand_loops = [] + for f in sel_faces: + for l in f.loops: + if l[uv_layer].select: + cand_loops.append(l) + + if len(cand_loops) < 2: + return None, "More than 2 UVs must be selected" + + first_loop = cand_loops[0] + isl_info = get_island_info_from_bmesh(bm, False) + loop_pairs = __get_loop_pairs(first_loop, uv_layer) + loop_pairs, err = __sort_loop_pairs(uv_layer, loop_pairs, closed) + if not loop_pairs: + return None, err + loop_seqs, err = __get_loop_sequence_internal(uv_layer, loop_pairs, + isl_info, closed) + if not loop_seqs: + return None, err + + return loop_seqs, "" diff --git a/uv_magic_uv/muv_common.py b/uv_magic_uv/muv_common.py deleted file mode 100644 index b52971ec..00000000 --- a/uv_magic_uv/muv_common.py +++ /dev/null @@ -1,83 +0,0 @@ -# <pep8-80 compliant> - -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -__author__ = "Nutti <nutti.metro@gmail.com>" -__status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" - -import bpy -from . import muv_props - - -def debug_print(*s): - """ - Print message to console in debugging mode - """ - - if muv_props.DEBUG: - print(s) - - -def check_version(major, minor, _): - """ - Check blender version - """ - - if bpy.app.version[0] == major and bpy.app.version[1] == minor: - return 0 - if bpy.app.version[0] > major: - return 1 - if bpy.app.version[1] > minor: - return 1 - return -1 - - -def redraw_all_areas(): - """ - Redraw all areas - """ - - for area in bpy.context.screen.areas: - area.tag_redraw() - - -def get_space(area_type, region_type, space_type): - """ - Get current area/region/space - """ - - area = None - region = None - space = None - - for area in bpy.context.screen.areas: - if area.type == area_type: - break - else: - return (None, None, None) - for region in area.regions: - if region.type == region_type: - break - for space in area.spaces: - if space.type == space_type: - break - - return (area, region, space) diff --git a/uv_magic_uv/muv_cpuv_selseq_ops.py b/uv_magic_uv/muv_cpuv_selseq_ops.py deleted file mode 100644 index 3cf69ff7..00000000 --- a/uv_magic_uv/muv_cpuv_selseq_ops.py +++ /dev/null @@ -1,279 +0,0 @@ -# <pep8-80 compliant> - -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -__author__ = "Nutti <nutti.metro@gmail.com>" -__status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" - -import bpy -import bmesh -from bpy.props import ( - StringProperty, - BoolProperty, - IntProperty, - EnumProperty, -) -from . import muv_common - - -class MUV_CPUVSelSeqCopyUV(bpy.types.Operator): - """ - Operation class: Copy UV coordinate by selection sequence - """ - - bl_idname = "uv.muv_cpuv_selseq_copy_uv" - bl_label = "Copy UV (Selection Sequence) (Operation)" - bl_description = "Copy UV data by selection sequence (Operation)" - bl_options = {'REGISTER', 'UNDO'} - - uv_map = StringProperty(options={'HIDDEN'}) - - def execute(self, context): - props = context.scene.muv_props.cpuv_selseq - if self.uv_map == "": - self.report({'INFO'}, "Copy UV coordinate (selection sequence)") - else: - self.report( - {'INFO'}, - "Copy UV coordinate (selection sequence) (UV map:%s)" - % (self.uv_map)) - obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: - bm.faces.ensure_lookup_table() - - # get UV layer - if self.uv_map == "": - if not bm.loops.layers.uv: - self.report( - {'WARNING'}, "Object must have more than one UV map") - return {'CANCELLED'} - uv_layer = bm.loops.layers.uv.verify() - else: - uv_layer = bm.loops.layers.uv[self.uv_map] - - # get selected face - props.src_uvs = [] - props.src_pin_uvs = [] - props.src_seams = [] - for hist in bm.select_history: - if isinstance(hist, bmesh.types.BMFace) and hist.select: - uvs = [l[uv_layer].uv.copy() for l in hist.loops] - pin_uvs = [l[uv_layer].pin_uv for l in hist.loops] - seams = [l.edge.seam for l in hist.loops] - props.src_uvs.append(uvs) - props.src_pin_uvs.append(pin_uvs) - props.src_seams.append(seams) - if not props.src_uvs or not props.src_pin_uvs: - self.report({'WARNING'}, "No faces are selected") - return {'CANCELLED'} - self.report({'INFO'}, "%d face(s) are selected" % len(props.src_uvs)) - - return {'FINISHED'} - - -class MUV_CPUVSelSeqCopyUVMenu(bpy.types.Menu): - """ - Menu class: Copy UV coordinate by selection sequence - """ - - bl_idname = "uv.muv_cpuv_selseq_copy_uv_menu" - bl_label = "Copy UV (Selection Sequence)" - bl_description = "Copy UV coordinate by selection sequence" - - def draw(self, context): - layout = self.layout - obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) - uv_maps = bm.loops.layers.uv.keys() - layout.operator( - MUV_CPUVSelSeqCopyUV.bl_idname, - text="[Default]", icon="IMAGE_COL").uv_map = "" - for m in uv_maps: - layout.operator( - MUV_CPUVSelSeqCopyUV.bl_idname, - text=m, icon="IMAGE_COL").uv_map = m - - -class MUV_CPUVSelSeqPasteUV(bpy.types.Operator): - """ - Operation class: Paste UV coordinate by selection sequence - """ - - bl_idname = "uv.muv_cpuv_selseq_paste_uv" - bl_label = "Paste UV (Selection Sequence) (Operation)" - bl_description = "Paste UV coordinate by selection sequence (Operation)" - bl_options = {'REGISTER', 'UNDO'} - - uv_map = StringProperty(options={'HIDDEN'}) - strategy = EnumProperty( - name="Strategy", - description="Paste Strategy", - items=[ - ('N_N', 'N:N', 'Number of faces must be equal to source'), - ('N_M', 'N:M', 'Number of faces must not be equal to source') - ], - default="N_M" - ) - flip_copied_uv = BoolProperty( - name="Flip Copied UV", - description="Flip Copied UV...", - default=False - ) - rotate_copied_uv = IntProperty( - default=0, - name="Rotate Copied UV", - min=0, - max=30 - ) - copy_seams = BoolProperty( - name="Copy Seams", - description="Copy Seams", - default=True - ) - - def execute(self, context): - props = context.scene.muv_props.cpuv_selseq - if not props.src_uvs or not props.src_pin_uvs: - self.report({'WARNING'}, "Need copy UV at first") - return {'CANCELLED'} - if self.uv_map == "": - self.report({'INFO'}, "Paste UV coordinate (selection sequence)") - else: - self.report( - {'INFO'}, - "Paste UV coordinate (selection sequence) (UV map:%s)" - % (self.uv_map)) - - obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: - bm.faces.ensure_lookup_table() - - # get UV layer - if self.uv_map == "": - if not bm.loops.layers.uv: - self.report( - {'WARNING'}, "Object must have more than one UV map") - return {'CANCELLED'} - uv_layer = bm.loops.layers.uv.verify() - else: - uv_layer = bm.loops.layers.uv[self.uv_map] - - # get selected face - dest_uvs = [] - dest_pin_uvs = [] - dest_seams = [] - dest_face_indices = [] - for hist in bm.select_history: - if isinstance(hist, bmesh.types.BMFace) and hist.select: - dest_face_indices.append(hist.index) - uvs = [l[uv_layer].uv.copy() for l in hist.loops] - pin_uvs = [l[uv_layer].pin_uv for l in hist.loops] - seams = [l.edge.seam for l in hist.loops] - dest_uvs.append(uvs) - dest_pin_uvs.append(pin_uvs) - dest_seams.append(seams) - if not dest_uvs or not dest_pin_uvs: - self.report({'WARNING'}, "No faces are selected") - return {'CANCELLED'} - if self.strategy == 'N_N' and len(props.src_uvs) != len(dest_uvs): - self.report( - {'WARNING'}, - "Number of selected faces is different from copied faces " + - "(src:%d, dest:%d)" - % (len(props.src_uvs), len(dest_uvs))) - return {'CANCELLED'} - - # paste - for i, idx in enumerate(dest_face_indices): - suv = None - spuv = None - ss = None - duv = None - if self.strategy == 'N_N': - suv = props.src_uvs[i] - spuv = props.src_pin_uvs[i] - ss = props.src_seams[i] - duv = dest_uvs[i] - elif self.strategy == 'N_M': - suv = props.src_uvs[i % len(props.src_uvs)] - spuv = props.src_pin_uvs[i % len(props.src_pin_uvs)] - ss = props.src_seams[i % len(props.src_seams)] - duv = dest_uvs[i] - if len(suv) != len(duv): - self.report({'WARNING'}, "Some faces are different size") - return {'CANCELLED'} - suvs_fr = [uv for uv in suv] - spuvs_fr = [pin_uv for pin_uv in spuv] - ss_fr = [s for s in ss] - # flip UVs - if self.flip_copied_uv is True: - suvs_fr.reverse() - spuvs_fr.reverse() - ss_fr.reverse() - # rotate UVs - for _ in range(self.rotate_copied_uv): - uv = suvs_fr.pop() - pin_uv = spuvs_fr.pop() - s = ss_fr.pop() - suvs_fr.insert(0, uv) - spuvs_fr.insert(0, pin_uv) - ss_fr.insert(0, s) - # paste UVs - for l, suv, spuv, ss in zip(bm.faces[idx].loops, suvs_fr, - spuvs_fr, ss_fr): - l[uv_layer].uv = suv - l[uv_layer].pin_uv = spuv - if self.copy_seams is True: - l.edge.seam = ss - - self.report({'INFO'}, "%d face(s) are copied" % len(dest_uvs)) - - bmesh.update_edit_mesh(obj.data) - if self.copy_seams is True: - obj.data.show_edge_seams = True - - return {'FINISHED'} - - -class MUV_CPUVSelSeqPasteUVMenu(bpy.types.Menu): - """ - Menu class: Paste UV coordinate by selection sequence - """ - - bl_idname = "uv.muv_cpuv_selseq_paste_uv_menu" - bl_label = "Paste UV (Selection Sequence)" - bl_description = "Paste UV coordinate by selection sequence" - - def draw(self, context): - layout = self.layout - # create sub menu - obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) - uv_maps = bm.loops.layers.uv.keys() - layout.operator( - MUV_CPUVSelSeqPasteUV.bl_idname, - text="[Default]", icon="IMAGE_COL").uv_map = "" - for m in uv_maps: - layout.operator( - MUV_CPUVSelSeqPasteUV.bl_idname, - text=m, icon="IMAGE_COL").uv_map = m diff --git a/uv_magic_uv/muv_menu.py b/uv_magic_uv/muv_menu.py deleted file mode 100644 index 47c79bbd..00000000 --- a/uv_magic_uv/muv_menu.py +++ /dev/null @@ -1,138 +0,0 @@ -# <pep8-80 compliant> - -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -__author__ = "Nutti <nutti.metro@gmail.com>" -__status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" - -import bpy -from . import muv_cpuv_ops -from . import muv_cpuv_selseq_ops -from . import muv_transuv_ops -from . import muv_texlock_ops -from . import muv_wsuv_ops -from . import muv_uvw_ops - - -class MUV_CPUVMenu(bpy.types.Menu): - """ - Menu class: Master menu of Copy/Paste UV coordinate - """ - - bl_idname = "uv.muv_cpuv_menu" - bl_label = "Copy/Paste UV" - bl_description = "Copy and Paste UV coordinate" - - def draw(self, _): - self.layout.menu( - muv_cpuv_ops.MUV_CPUVCopyUVMenu.bl_idname, icon="IMAGE_COL") - self.layout.menu( - muv_cpuv_ops.MUV_CPUVPasteUVMenu.bl_idname, icon="IMAGE_COL") - self.layout.menu( - muv_cpuv_selseq_ops.MUV_CPUVSelSeqCopyUVMenu.bl_idname, - icon="IMAGE_COL") - self.layout.menu( - muv_cpuv_selseq_ops.MUV_CPUVSelSeqPasteUVMenu.bl_idname, - icon="IMAGE_COL") - - -class MUV_CPUVObjMenu(bpy.types.Menu): - """ - Menu class: Master menu of Copy/Paste UV coordinate per object - """ - - bl_idname = "object.muv_cpuv_obj_menu" - bl_label = "Copy/Paste UV" - bl_description = "Copy and Paste UV coordinate per object" - - def draw(self, _): - self.layout.menu( - muv_cpuv_ops.MUV_CPUVObjCopyUVMenu.bl_idname, icon="IMAGE_COL") - self.layout.menu( - muv_cpuv_ops.MUV_CPUVObjPasteUVMenu.bl_idname, icon="IMAGE_COL") - - -class MUV_TransUVMenu(bpy.types.Menu): - """ - Menu class: Master menu of Transfer UV coordinate - """ - - bl_idname = "uv.muv_transuv_menu" - bl_label = "Transfer UV" - bl_description = "Transfer UV coordinate" - - def draw(self, _): - self.layout.operator( - muv_transuv_ops.MUV_TransUVCopy.bl_idname, icon="IMAGE_COL") - self.layout.operator( - muv_transuv_ops.MUV_TransUVPaste.bl_idname, icon="IMAGE_COL") - - -class MUV_TexLockMenu(bpy.types.Menu): - """ - Menu class: Master menu of Texture Lock - """ - - bl_idname = "uv.muv_texlock_menu" - bl_label = "Texture Lock" - bl_description = "Lock texture when vertices of mesh (Preserve UV)" - - def draw(self, _): - self.layout.operator( - muv_texlock_ops.MUV_TexLockStart.bl_idname, icon="IMAGE_COL") - self.layout.operator( - muv_texlock_ops.MUV_TexLockStop.bl_idname, icon="IMAGE_COL") - self.layout.operator( - muv_texlock_ops.MUV_TexLockIntrStart.bl_idname, icon="IMAGE_COL") - self.layout.operator( - muv_texlock_ops.MUV_TexLockIntrStop.bl_idname, icon="IMAGE_COL") - - -class MUV_WSUVMenu(bpy.types.Menu): - """ - Menu class: Master menu of world scale UV - """ - - bl_idname = "uv.muv_wsuv_menu" - bl_label = "World Scale UV" - bl_description = "" - - def draw(self, _): - self.layout.operator( - muv_wsuv_ops.MUV_WSUVMeasure.bl_idname, icon="IMAGE_COL") - self.layout.operator( - muv_wsuv_ops.MUV_WSUVApply.bl_idname, icon="IMAGE_COL") - - -class MUV_UVWMenu(bpy.types.Menu): - """ - Menu class: Master menu of UVW - """ - - bl_idname = "uv.muv_uvw_menu" - bl_label = "UVW" - bl_description = "" - - def draw(self, _): - self.layout.operator( - muv_uvw_ops.MUV_UVWBoxMap.bl_idname, icon="IMAGE_COL") - self.layout.operator( - muv_uvw_ops.MUV_UVWBestPlanerMap.bl_idname, icon="IMAGE_COL") diff --git a/uv_magic_uv/muv_preferences.py b/uv_magic_uv/muv_preferences.py deleted file mode 100644 index e14ce99b..00000000 --- a/uv_magic_uv/muv_preferences.py +++ /dev/null @@ -1,144 +0,0 @@ -# <pep8-80 compliant> - -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -__author__ = "Nutti <nutti.metro@gmail.com>" -__status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" - -from bpy.props import ( - BoolProperty, - FloatProperty, - FloatVectorProperty, -) -from bpy.types import AddonPreferences - - -class MUV_Preferences(AddonPreferences): - """Preferences class: Preferences for this add-on""" - - bl_idname = __package__ - - # enable/disable switcher - enable_texproj = BoolProperty( - name="Texture Projection", - default=True) - enable_uvbb = BoolProperty( - name="Bounding Box", - default=True) - - # for Texture Projection - texproj_canvas_padding = FloatVectorProperty( - name="Canvas Padding", - description="Canvas Padding", - size=2, - max=50.0, - min=0.0, - default=(20.0, 20.0)) - - # for UV Bounding Box - uvbb_cp_size = FloatProperty( - name="Size", - description="Control Point Size", - default=6.0, - min=3.0, - max=100.0) - uvbb_cp_react_size = FloatProperty( - name="React Size", - description="Size event fired", - default=10.0, - min=3.0, - max=100.0) - - def draw(self, _): - layout = self.layout - - layout.label("Switch Enable/Disable and Configurate Features:") - - layout.prop(self, "enable_texproj") - if self.enable_texproj: - sp = layout.split(percentage=0.05) - col = sp.column() # spacer - sp = sp.split(percentage=0.3) - col = sp.column() - col.label("Texture Display: ") - col.prop(self, "texproj_canvas_padding") - - layout.prop(self, "enable_uvbb") - if self.enable_uvbb: - sp = layout.split(percentage=0.05) - col = sp.column() # spacer - sp = sp.split(percentage=0.3) - col = sp.column() - col.label("Control Point: ") - col.prop(self, "uvbb_cp_size") - col.prop(self, "uvbb_cp_react_size") - - layout.label("Description:") - column = layout.column(align=True) - column.label("Magic UV is composed of many UV editing features.") - column.label("See tutorial page if you are new to this add-on.") - column.label("https://github.com/nutti/Magic-UV/wiki/Tutorial") - - layout.label("Location:") - - row = layout.row(align=True) - sp = row.split(percentage=0.3) - sp.label("View3D > U") - sp = sp.split(percentage=1.0) - col = sp.column(align=True) - col.label("Copy/Paste UV Coordinates") - col.label("Copy/Paste UV Coordinates (by selection sequence)") - col.label("Flip/Rotate UVs") - col.label("Transfer UV") - col.label("Move UV from 3D View") - col.label("Texture Lock") - col.label("Mirror UV") - col.label("World Scale UV") - col.label("Unwrap Constraint") - col.label("Preserve UV Aspect") - - row = layout.row(align=True) - sp = row.split(percentage=0.3) - sp.label("View3D > Object") - sp = sp.split(percentage=1.0) - col = sp.column(align=True) - col.label("Copy/Paste UV Coordinates (Among same objects)") - - row = layout.row(align=True) - sp = row.split(percentage=0.3) - sp.label("ImageEditor > Property Panel") - sp = sp.split(percentage=1.0) - col = sp.column(align=True) - col.label("Manipulate UV with Bounding Box in UV Editor") - - row = layout.row(align=True) - sp = row.split(percentage=0.3) - sp.label("View3D > Property Panel") - sp = sp.split(percentage=1.0) - col = sp.column(align=True) - col.label("Texture Projection") - - row = layout.row(align=True) - sp = row.split(percentage=0.3) - sp.label("ImageEditor > UVs") - sp = sp.split(percentage=1.0) - col = sp.column(align=True) - col.label("Pack UV (with same UV island packing)") diff --git a/uv_magic_uv/muv_props.py b/uv_magic_uv/muv_props.py deleted file mode 100644 index c0a7d961..00000000 --- a/uv_magic_uv/muv_props.py +++ /dev/null @@ -1,148 +0,0 @@ -# <pep8-80 compliant> - -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -__author__ = "Nutti <nutti.metro@gmail.com>" -__status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" - -import bpy -from bpy.props import ( - FloatProperty, - EnumProperty, - BoolProperty, -) - - -DEBUG = False - - -def get_loaded_texture_name(_, __): - items = [(key, key, "") for key in bpy.data.images.keys()] - items.append(("None", "None", "")) - return items - - -# Properties used in this add-on. -class MUV_Properties(): - cpuv = None - cpuv_obj = None - cpuv_selseq = None - transuv = None - uvbb = None - texproj = None - texlock = None - texwrap = None - wsuv = None - - def __init__(self): - self.cpuv = MUV_CPUVProps() - self.cpuv_obj = MUV_CPUVProps() - self.cpuv_selseq = MUV_CPUVSelSeqProps() - self.transuv = MUV_TransUVProps() - self.uvbb = MUV_UVBBProps() - self.texproj = MUV_TexProjProps() - self.texlock = MUV_TexLockProps() - self.texwrap = MUV_TexWrapProps() - self.wsuv = MUV_WSUVProps() - - -class MUV_CPUVProps(): - src_uvs = [] - src_pin_uvs = [] - src_seams = [] - - -class MUV_CPUVSelSeqProps(): - src_uvs = [] - src_pin_uvs = [] - src_seams = [] - - -class MUV_TransUVProps(): - topology_copied = [] - - -class MUV_UVBBProps(): - uv_info_ini = [] - ctrl_points_ini = [] - ctrl_points = [] - running = False - - -class MUV_TexProjProps(): - running = False - - -class MUV_TexLockProps(): - verts_orig = None - intr_verts_orig = None - intr_running = False - - -class MUV_TexWrapProps(): - src_face_index = -1 - - -class MUV_WSUVProps(): - ref_sv = None - ref_suv = None - - -def init_props(scene): - scene.muv_props = MUV_Properties() - scene.muv_uvbb_uniform_scaling = BoolProperty( - name="Uniform Scaling", - description="Enable Uniform Scaling", - default=False) - scene.muv_texproj_tex_magnitude = FloatProperty( - name="Magnitude", - description="Texture Magnitude", - default=0.5, - min=0.0, - max=100.0) - scene.muv_texproj_tex_image = EnumProperty( - name="Image", - description="Texture Image", - items=get_loaded_texture_name) - scene.muv_texproj_tex_transparency = FloatProperty( - name="Transparency", - description="Texture Transparency", - default=0.2, - min=0.0, - max=1.0) - scene.muv_texproj_adjust_window = BoolProperty( - name="Adjust Window", - description="Size of renderered texture is fitted to window", - default=True) - scene.muv_texproj_apply_tex_aspect = BoolProperty( - name="Texture Aspect Ratio", - description="Apply Texture Aspect ratio to displayed texture", - default=True) - - -def clear_props(scene): - del scene.muv_props - del scene.muv_uvbb_uniform_scaling - del scene.muv_texproj_tex_magnitude - del scene.muv_texproj_tex_image - del scene.muv_texproj_tex_transparency - del scene.muv_texproj_adjust_window - del scene.muv_texproj_apply_tex_aspect diff --git a/uv_magic_uv/op/__init__.py b/uv_magic_uv/op/__init__.py new file mode 100644 index 00000000..75885ef6 --- /dev/null +++ b/uv_magic_uv/op/__init__.py @@ -0,0 +1,72 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +if "bpy" in locals(): + import importlib + importlib.reload(align_uv) + importlib.reload(align_uv_cursor) + importlib.reload(copy_paste_uv) + importlib.reload(copy_paste_uv_object) + importlib.reload(copy_paste_uv_uvedit) + importlib.reload(flip_rotate_uv) + importlib.reload(mirror_uv) + importlib.reload(move_uv) + importlib.reload(pack_uv) + importlib.reload(preserve_uv_aspect) + importlib.reload(smooth_uv) + importlib.reload(texture_lock) + importlib.reload(texture_projection) + importlib.reload(texture_wrap) + importlib.reload(transfer_uv) + importlib.reload(unwrap_constraint) + importlib.reload(uv_bounding_box) + importlib.reload(uv_inspection) + importlib.reload(uv_sculpt) + importlib.reload(uvw) + importlib.reload(world_scale_uv) +else: + from . import align_uv + from . import align_uv_cursor + from . import copy_paste_uv + from . import copy_paste_uv_object + from . import copy_paste_uv_uvedit + from . import flip_rotate_uv + from . import mirror_uv + from . import move_uv + from . import pack_uv + from . import preserve_uv_aspect + from . import smooth_uv + from . import texture_lock + from . import texture_projection + from . import texture_wrap + from . import transfer_uv + from . import unwrap_constraint + from . import uv_bounding_box + from . import uv_inspection + from . import uv_sculpt + from . import uvw + from . import world_scale_uv + +import bpy diff --git a/uv_magic_uv/op/align_uv.py b/uv_magic_uv/op/align_uv.py new file mode 100644 index 00000000..dcfb57c3 --- /dev/null +++ b/uv_magic_uv/op/align_uv.py @@ -0,0 +1,757 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "imdjs, Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import math +from math import atan2, tan, sin, cos + +import bpy +import bmesh +from mathutils import Vector +from bpy.props import EnumProperty, BoolProperty + +from .. import common + + +# get sum vertex length of loop sequences +def get_loop_vert_len(loops): + length = 0 + for l1, l2 in zip(loops[:-1], loops[1:]): + diff = l2.vert.co - l1.vert.co + length = length + abs(diff.length) + + return length + + +# get sum uv length of loop sequences +def get_loop_uv_len(loops, uv_layer): + length = 0 + for l1, l2 in zip(loops[:-1], loops[1:]): + diff = l2[uv_layer].uv - l1[uv_layer].uv + length = length + abs(diff.length) + + return length + + +# get center/radius of circle by 3 vertices +def get_circle(v): + alpha = atan2((v[0].y - v[1].y), (v[0].x - v[1].x)) + math.pi / 2 + beta = atan2((v[1].y - v[2].y), (v[1].x - v[2].x)) + math.pi / 2 + ex = (v[0].x + v[1].x) / 2.0 + ey = (v[0].y + v[1].y) / 2.0 + fx = (v[1].x + v[2].x) / 2.0 + fy = (v[1].y + v[2].y) / 2.0 + cx = (ey - fy - ex * tan(alpha) + fx * tan(beta)) / \ + (tan(beta) - tan(alpha)) + cy = ey - (ex - cx) * tan(alpha) + center = Vector((cx, cy)) + + r = v[0] - center + radian = r.length + + return center, radian + + +# get position on circle with same arc length +def calc_v_on_circle(v, center, radius): + base = v[0] + theta = atan2(base.y - center.y, base.x - center.x) + new_v = [] + for i in range(len(v)): + angle = theta + i * 2 * math.pi / len(v) + new_v.append(Vector((center.x + radius * sin(angle), + center.y + radius * cos(angle)))) + + return new_v + + +class MUV_AUVCircle(bpy.types.Operator): + + bl_idname = "uv.muv_auv_circle" + bl_label = "Circle" + bl_description = "Align UV coordinates to Circle" + bl_options = {'REGISTER', 'UNDO'} + + transmission = BoolProperty( + name="Transmission", + description="Align linked UVs", + default=False + ) + select = BoolProperty( + name="Select", + description="Select UVs which are aligned", + default=False + ) + + @classmethod + def poll(cls, context): + return context.mode == 'EDIT_MESH' + + def execute(self, context): + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + uv_layer = bm.loops.layers.uv.verify() + + # loop_seqs[horizontal][vertical][loop] + loop_seqs, error = common.get_loop_sequences(bm, uv_layer, True) + if not loop_seqs: + self.report({'WARNING'}, error) + return {'CANCELLED'} + + # get circle and new UVs + uvs = [hseq[0][0][uv_layer].uv.copy() for hseq in loop_seqs] + c, r = get_circle(uvs[0:3]) + new_uvs = calc_v_on_circle(uvs, c, r) + + # check center UV of circle + center = loop_seqs[0][-1][0].vert + for hseq in loop_seqs[1:]: + if len(hseq[-1]) != 1: + self.report({'WARNING'}, "Last face must be triangle") + return {'CANCELLED'} + if hseq[-1][0].vert != center: + self.report({'WARNING'}, "Center must be identical") + return {'CANCELLED'} + + # align to circle + if self.transmission: + for hidx, hseq in enumerate(loop_seqs): + for vidx, pair in enumerate(hseq): + all_ = int((len(hseq) + 1) / 2) + r = (all_ - int((vidx + 1) / 2)) / all_ + pair[0][uv_layer].uv = c + (new_uvs[hidx] - c) * r + if self.select: + pair[0][uv_layer].select = True + + if len(pair) < 2: + continue + # for quad polygon + next_hidx = (hidx + 1) % len(loop_seqs) + pair[1][uv_layer].uv = c + ((new_uvs[next_hidx]) - c) * r + if self.select: + pair[1][uv_layer].select = True + else: + for hidx, hseq in enumerate(loop_seqs): + pair = hseq[0] + pair[0][uv_layer].uv = new_uvs[hidx] + pair[1][uv_layer].uv = new_uvs[(hidx + 1) % len(loop_seqs)] + if self.select: + pair[0][uv_layer].select = True + pair[1][uv_layer].select = True + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} + + +# get horizontal differential of UV influenced by mesh vertex +def get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, pair_idx): + common.debug_print( + "vidx={0}, hidx={1}, pair_idx={2}".format(vidx, hidx, pair_idx)) + + # get total vertex length + hloops = [] + for s in loop_seqs: + hloops.extend([s[vidx][0], s[vidx][1]]) + vert_total_hlen = get_loop_vert_len(hloops) + common.debug_print(vert_total_hlen) + + # target vertex length + hloops = [] + for s in loop_seqs[:hidx]: + hloops.extend([s[vidx][0], s[vidx][1]]) + for pidx, l in enumerate(loop_seqs[hidx][vidx]): + if pidx > pair_idx: + break + hloops.append(l) + vert_hlen = get_loop_vert_len(hloops) + common.debug_print(vert_hlen) + + # get total UV length + # uv_all_hdiff = loop_seqs[-1][0][-1][uv_layer].uv - + # loop_seqs[0][0][0][uv_layer].uv + uv_total_hlen = loop_seqs[-1][vidx][-1][uv_layer].uv -\ + loop_seqs[0][vidx][0][uv_layer].uv + common.debug_print(uv_total_hlen) + + return uv_total_hlen * vert_hlen / vert_total_hlen + + +# get vertical differential of UV influenced by mesh vertex +def get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, pair_idx): + common.debug_print( + "vidx={0}, hidx={1}, pair_idx={2}".format(vidx, hidx, pair_idx)) + + # get total vertex length + hloops = [] + for s in loop_seqs[hidx]: + hloops.append(s[pair_idx]) + vert_total_hlen = get_loop_vert_len(hloops) + common.debug_print(vert_total_hlen) + + # target vertex length + hloops = [] + for s in loop_seqs[hidx][:vidx + 1]: + hloops.append(s[pair_idx]) + vert_hlen = get_loop_vert_len(hloops) + common.debug_print(vert_hlen) + + # get total UV length + # uv_all_hdiff = loop_seqs[0][-1][pair_idx][uv_layer].uv - \ + # loop_seqs[0][0][pair_idx][uv_layer].uv + uv_total_hlen = loop_seqs[hidx][-1][pair_idx][uv_layer].uv -\ + loop_seqs[hidx][0][pair_idx][uv_layer].uv + common.debug_print(uv_total_hlen) + + return uv_total_hlen * vert_hlen / vert_total_hlen + + +# get horizontal differential of UV no influenced +def get_hdiff_uv(uv_layer, loop_seqs, hidx): + base_uv = loop_seqs[0][0][0][uv_layer].uv.copy() + h_uv = loop_seqs[-1][0][1][uv_layer].uv.copy() - base_uv + + return hidx * h_uv / len(loop_seqs) + + +# get vertical differential of UV no influenced +def get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx): + base_uv = loop_seqs[0][0][0][uv_layer].uv.copy() + v_uv = loop_seqs[0][-1][0][uv_layer].uv.copy() - base_uv + + hseq = loop_seqs[hidx] + return int((vidx + 1) / 2) * v_uv / (len(hseq) / 2) + + +class MUV_AUVStraighten(bpy.types.Operator): + + bl_idname = "uv.muv_auv_straighten" + bl_label = "Straighten" + bl_description = "Straighten UV coordinates" + bl_options = {'REGISTER', 'UNDO'} + + transmission = BoolProperty( + name="Transmission", + description="Align linked UVs", + default=False + ) + select = BoolProperty( + name="Select", + description="Select UVs which are aligned", + default=False + ) + vertical = BoolProperty( + name="Vert-Infl (Vertical)", + description="Align vertical direction influenced " + "by mesh vertex proportion", + default=False + ) + horizontal = BoolProperty( + name="Vert-Infl (Horizontal)", + description="Align horizontal direction influenced " + "by mesh vertex proportion", + default=False + ) + + @classmethod + def poll(cls, context): + return context.mode == 'EDIT_MESH' + + # selected and paralleled UV loop sequence will be aligned + def __align_w_transmission(self, loop_seqs, uv_layer): + base_uv = loop_seqs[0][0][0][uv_layer].uv.copy() + + # calculate diff UVs + diff_uvs = [] + # hseq[vertical][loop] + for hidx, hseq in enumerate(loop_seqs): + # pair[loop] + diffs = [] + for vidx in range(0, len(hseq), 2): + if self.horizontal: + hdiff_uvs = [ + get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0), + get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1), + get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1, + hidx, 0), + get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1, + hidx, 1), + ] + else: + hdiff_uvs = [ + get_hdiff_uv(uv_layer, loop_seqs, hidx), + get_hdiff_uv(uv_layer, loop_seqs, hidx + 1), + get_hdiff_uv(uv_layer, loop_seqs, hidx), + get_hdiff_uv(uv_layer, loop_seqs, hidx + 1) + ] + if self.vertical: + vdiff_uvs = [ + get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0), + get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1), + get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1, + hidx, 0), + get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1, + hidx, 1), + ] + else: + vdiff_uvs = [ + get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx), + get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx), + get_vdiff_uv(uv_layer, loop_seqs, vidx + 1, hidx), + get_vdiff_uv(uv_layer, loop_seqs, vidx + 1, hidx) + ] + diffs.append([hdiff_uvs, vdiff_uvs]) + diff_uvs.append(diffs) + + # update UV + for hseq, diffs in zip(loop_seqs, diff_uvs): + for vidx in range(0, len(hseq), 2): + loops = [ + hseq[vidx][0], hseq[vidx][1], + hseq[vidx + 1][0], hseq[vidx + 1][1] + ] + for l, hdiff, vdiff in zip(loops, diffs[int(vidx / 2)][0], + diffs[int(vidx / 2)][1]): + l[uv_layer].uv = base_uv + hdiff + vdiff + if self.select: + l[uv_layer].select = True + + # only selected UV loop sequence will be aligned + def __align_wo_transmission(self, loop_seqs, uv_layer): + base_uv = loop_seqs[0][0][0][uv_layer].uv.copy() + + h_uv = loop_seqs[-1][0][1][uv_layer].uv.copy() - base_uv + for hidx, hseq in enumerate(loop_seqs): + # only selected loop pair is targeted + pair = hseq[0] + hdiff_uv_0 = hidx * h_uv / len(loop_seqs) + hdiff_uv_1 = (hidx + 1) * h_uv / len(loop_seqs) + pair[0][uv_layer].uv = base_uv + hdiff_uv_0 + pair[1][uv_layer].uv = base_uv + hdiff_uv_1 + if self.select: + pair[0][uv_layer].select = True + pair[1][uv_layer].select = True + + def __align(self, loop_seqs, uv_layer): + if self.transmission: + self.__align_w_transmission(loop_seqs, uv_layer) + else: + self.__align_wo_transmission(loop_seqs, uv_layer) + + def execute(self, context): + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + uv_layer = bm.loops.layers.uv.verify() + + # loop_seqs[horizontal][vertical][loop] + loop_seqs, error = common.get_loop_sequences(bm, uv_layer) + if not loop_seqs: + self.report({'WARNING'}, error) + return {'CANCELLED'} + + # align + self.__align(loop_seqs, uv_layer) + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} + + +class MUV_AUVAxis(bpy.types.Operator): + + bl_idname = "uv.muv_auv_axis" + bl_label = "XY-Axis" + bl_description = "Align UV to XY-axis" + bl_options = {'REGISTER', 'UNDO'} + + transmission = BoolProperty( + name="Transmission", + description="Align linked UVs", + default=False + ) + select = BoolProperty( + name="Select", + description="Select UVs which are aligned", + default=False + ) + vertical = BoolProperty( + name="Vert-Infl (Vertical)", + description="Align vertical direction influenced " + "by mesh vertex proportion", + default=False + ) + horizontal = BoolProperty( + name="Vert-Infl (Horizontal)", + description="Align horizontal direction influenced " + "by mesh vertex proportion", + default=False + ) + location = EnumProperty( + name="Location", + description="Align location", + items=[ + ('LEFT_TOP', "Left/Top", "Align to Left or Top"), + ('MIDDLE', "Middle", "Align to middle"), + ('RIGHT_BOTTOM', "Right/Bottom", "Align to Right or Bottom") + ], + default='MIDDLE' + ) + + @classmethod + def poll(cls, context): + return context.mode == 'EDIT_MESH' + + # get min/max of UV + def __get_uv_max_min(self, loop_seqs, uv_layer): + uv_max = Vector((-1000000.0, -1000000.0)) + uv_min = Vector((1000000.0, 1000000.0)) + for hseq in loop_seqs: + for l in hseq[0]: + uv = l[uv_layer].uv + uv_max.x = max(uv.x, uv_max.x) + uv_max.y = max(uv.y, uv_max.y) + uv_min.x = min(uv.x, uv_min.x) + uv_min.y = min(uv.y, uv_min.y) + + return uv_max, uv_min + + # get UV differentiation when UVs are aligned to X-axis + def __get_x_axis_align_diff_uvs(self, loop_seqs, uv_layer, uv_min, + width, height): + diff_uvs = [] + for hidx, hseq in enumerate(loop_seqs): + pair = hseq[0] + luv0 = pair[0][uv_layer] + luv1 = pair[1][uv_layer] + target_uv0 = Vector((0.0, 0.0)) + target_uv1 = Vector((0.0, 0.0)) + if self.location == 'RIGHT_BOTTOM': + target_uv0.y = target_uv1.y = uv_min.y + elif self.location == 'MIDDLE': + target_uv0.y = target_uv1.y = uv_min.y + height * 0.5 + elif self.location == 'LEFT_TOP': + target_uv0.y = target_uv1.y = uv_min.y + height + if luv0.uv.x < luv1.uv.x: + target_uv0.x = uv_min.x + hidx * width / len(loop_seqs) + target_uv1.x = uv_min.x + (hidx + 1) * width / len(loop_seqs) + else: + target_uv0.x = uv_min.x + (hidx + 1) * width / len(loop_seqs) + target_uv1.x = uv_min.x + hidx * width / len(loop_seqs) + diff_uvs.append([target_uv0 - luv0.uv, target_uv1 - luv1.uv]) + + return diff_uvs + + # get UV differentiation when UVs are aligned to Y-axis + def __get_y_axis_align_diff_uvs(self, loop_seqs, uv_layer, uv_min, + width, height): + diff_uvs = [] + for hidx, hseq in enumerate(loop_seqs): + pair = hseq[0] + luv0 = pair[0][uv_layer] + luv1 = pair[1][uv_layer] + target_uv0 = Vector((0.0, 0.0)) + target_uv1 = Vector((0.0, 0.0)) + if self.location == 'RIGHT_BOTTOM': + target_uv0.x = target_uv1.x = uv_min.x + width + elif self.location == 'MIDDLE': + target_uv0.x = target_uv1.x = uv_min.x + width * 0.5 + elif self.location == 'LEFT_TOP': + target_uv0.x = target_uv1.x = uv_min.x + if luv0.uv.y < luv1.uv.y: + target_uv0.y = uv_min.y + hidx * height / len(loop_seqs) + target_uv1.y = uv_min.y + (hidx + 1) * height / len(loop_seqs) + else: + target_uv0.y = uv_min.y + (hidx + 1) * height / len(loop_seqs) + target_uv1.y = uv_min.y + hidx * height / len(loop_seqs) + diff_uvs.append([target_uv0 - luv0.uv, target_uv1 - luv1.uv]) + + return diff_uvs + + # only selected UV loop sequence will be aligned along to X-axis + def __align_to_x_axis_wo_transmission(self, loop_seqs, uv_layer, + uv_min, width, height): + # reverse if the UV coordinate is not sorted by position + need_revese = loop_seqs[0][0][0][uv_layer].uv.x > \ + loop_seqs[-1][0][0][uv_layer].uv.x + if need_revese: + loop_seqs.reverse() + for hidx, hseq in enumerate(loop_seqs): + for vidx, pair in enumerate(hseq): + tmp = loop_seqs[hidx][vidx][0] + loop_seqs[hidx][vidx][0] = loop_seqs[hidx][vidx][1] + loop_seqs[hidx][vidx][1] = tmp + + # get UV differential + diff_uvs = self.__get_x_axis_align_diff_uvs(loop_seqs, uv_layer, + uv_min, width, height) + + # update UV + for hseq, duv in zip(loop_seqs, diff_uvs): + pair = hseq[0] + luv0 = pair[0][uv_layer] + luv1 = pair[1][uv_layer] + luv0.uv = luv0.uv + duv[0] + luv1.uv = luv1.uv + duv[1] + + # only selected UV loop sequence will be aligned along to Y-axis + def __align_to_y_axis_wo_transmission(self, loop_seqs, uv_layer, + uv_min, width, height): + # reverse if the UV coordinate is not sorted by position + need_revese = loop_seqs[0][0][0][uv_layer].uv.y > \ + loop_seqs[-1][0][0][uv_layer].uv.y + if need_revese: + loop_seqs.reverse() + for hidx, hseq in enumerate(loop_seqs): + for vidx, pair in enumerate(hseq): + tmp = loop_seqs[hidx][vidx][0] + loop_seqs[hidx][vidx][0] = loop_seqs[hidx][vidx][1] + loop_seqs[hidx][vidx][1] = tmp + + # get UV differential + diff_uvs = self.__get_y_axis_align_diff_uvs(loop_seqs, uv_layer, + uv_min, width, height) + + # update UV + for hseq, duv in zip(loop_seqs, diff_uvs): + pair = hseq[0] + luv0 = pair[0][uv_layer] + luv1 = pair[1][uv_layer] + luv0.uv = luv0.uv + duv[0] + luv1.uv = luv1.uv + duv[1] + + # selected and paralleled UV loop sequence will be aligned along to X-axis + def __align_to_x_axis_w_transmission(self, loop_seqs, uv_layer, + uv_min, width, height): + # reverse if the UV coordinate is not sorted by position + need_revese = loop_seqs[0][0][0][uv_layer].uv.x > \ + loop_seqs[-1][0][0][uv_layer].uv.x + if need_revese: + loop_seqs.reverse() + for hidx, hseq in enumerate(loop_seqs): + for vidx in range(len(hseq)): + tmp = loop_seqs[hidx][vidx][0] + loop_seqs[hidx][vidx][0] = loop_seqs[hidx][vidx][1] + loop_seqs[hidx][vidx][1] = tmp + + # get offset UVs when the UVs are aligned to X-axis + align_diff_uvs = self.__get_x_axis_align_diff_uvs(loop_seqs, uv_layer, + uv_min, width, + height) + base_uv = loop_seqs[0][0][0][uv_layer].uv.copy() + offset_uvs = [] + for hseq, aduv in zip(loop_seqs, align_diff_uvs): + luv0 = hseq[0][0][uv_layer] + luv1 = hseq[0][1][uv_layer] + offset_uvs.append([luv0.uv + aduv[0] - base_uv, + luv1.uv + aduv[1] - base_uv]) + + # get UV differential + diff_uvs = [] + # hseq[vertical][loop] + for hidx, hseq in enumerate(loop_seqs): + # pair[loop] + diffs = [] + for vidx in range(0, len(hseq), 2): + if self.horizontal: + hdiff_uvs = [ + get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0), + get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1), + get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1, + hidx, 0), + get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1, + hidx, 1), + ] + hdiff_uvs[0].y = hdiff_uvs[0].y + offset_uvs[hidx][0].y + hdiff_uvs[1].y = hdiff_uvs[1].y + offset_uvs[hidx][1].y + hdiff_uvs[2].y = hdiff_uvs[2].y + offset_uvs[hidx][0].y + hdiff_uvs[3].y = hdiff_uvs[3].y + offset_uvs[hidx][1].y + else: + hdiff_uvs = [ + offset_uvs[hidx][0], + offset_uvs[hidx][1], + offset_uvs[hidx][0], + offset_uvs[hidx][1], + ] + if self.vertical: + vdiff_uvs = [ + get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0), + get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1), + get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1, + hidx, 0), + get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1, + hidx, 1), + ] + else: + vdiff_uvs = [ + get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx), + get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx), + get_vdiff_uv(uv_layer, loop_seqs, vidx + 1, hidx), + get_vdiff_uv(uv_layer, loop_seqs, vidx + 1, hidx) + ] + diffs.append([hdiff_uvs, vdiff_uvs]) + diff_uvs.append(diffs) + + # update UV + for hseq, diffs in zip(loop_seqs, diff_uvs): + for vidx in range(0, len(hseq), 2): + loops = [ + hseq[vidx][0], hseq[vidx][1], + hseq[vidx + 1][0], hseq[vidx + 1][1] + ] + for l, hdiff, vdiff in zip(loops, diffs[int(vidx / 2)][0], + diffs[int(vidx / 2)][1]): + l[uv_layer].uv = base_uv + hdiff + vdiff + if self.select: + l[uv_layer].select = True + + # selected and paralleled UV loop sequence will be aligned along to Y-axis + def __align_to_y_axis_w_transmission(self, loop_seqs, uv_layer, + uv_min, width, height): + # reverse if the UV coordinate is not sorted by position + need_revese = loop_seqs[0][0][0][uv_layer].uv.y > \ + loop_seqs[-1][0][-1][uv_layer].uv.y + if need_revese: + loop_seqs.reverse() + for hidx, hseq in enumerate(loop_seqs): + for vidx in range(len(hseq)): + tmp = loop_seqs[hidx][vidx][0] + loop_seqs[hidx][vidx][0] = loop_seqs[hidx][vidx][1] + loop_seqs[hidx][vidx][1] = tmp + + # get offset UVs when the UVs are aligned to Y-axis + align_diff_uvs = self.__get_y_axis_align_diff_uvs(loop_seqs, uv_layer, + uv_min, width, + height) + base_uv = loop_seqs[0][0][0][uv_layer].uv.copy() + offset_uvs = [] + for hseq, aduv in zip(loop_seqs, align_diff_uvs): + luv0 = hseq[0][0][uv_layer] + luv1 = hseq[0][1][uv_layer] + offset_uvs.append([luv0.uv + aduv[0] - base_uv, + luv1.uv + aduv[1] - base_uv]) + + # get UV differential + diff_uvs = [] + # hseq[vertical][loop] + for hidx, hseq in enumerate(loop_seqs): + # pair[loop] + diffs = [] + for vidx in range(0, len(hseq), 2): + if self.horizontal: + hdiff_uvs = [ + get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0), + get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1), + get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1, + hidx, 0), + get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1, + hidx, 1), + ] + hdiff_uvs[0].x = hdiff_uvs[0].x + offset_uvs[hidx][0].x + hdiff_uvs[1].x = hdiff_uvs[1].x + offset_uvs[hidx][1].x + hdiff_uvs[2].x = hdiff_uvs[2].x + offset_uvs[hidx][0].x + hdiff_uvs[3].x = hdiff_uvs[3].x + offset_uvs[hidx][1].x + else: + hdiff_uvs = [ + offset_uvs[hidx][0], + offset_uvs[hidx][1], + offset_uvs[hidx][0], + offset_uvs[hidx][1], + ] + if self.vertical: + vdiff_uvs = [ + get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 0), + get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, 1), + get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1, + hidx, 0), + get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx + 1, + hidx, 1), + ] + else: + vdiff_uvs = [ + get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx), + get_vdiff_uv(uv_layer, loop_seqs, vidx, hidx), + get_vdiff_uv(uv_layer, loop_seqs, vidx + 1, hidx), + get_vdiff_uv(uv_layer, loop_seqs, vidx + 1, hidx) + ] + diffs.append([hdiff_uvs, vdiff_uvs]) + diff_uvs.append(diffs) + + # update UV + for hseq, diffs in zip(loop_seqs, diff_uvs): + for vidx in range(0, len(hseq), 2): + loops = [ + hseq[vidx][0], hseq[vidx][1], + hseq[vidx + 1][0], hseq[vidx + 1][1] + ] + for l, hdiff, vdiff in zip(loops, diffs[int(vidx / 2)][0], + diffs[int(vidx / 2)][1]): + l[uv_layer].uv = base_uv + hdiff + vdiff + if self.select: + l[uv_layer].select = True + + def __align(self, loop_seqs, uv_layer, uv_min, width, height): + # align along to x-axis + if width > height: + if self.transmission: + self.__align_to_x_axis_w_transmission(loop_seqs, uv_layer, + uv_min, width, height) + else: + self.__align_to_x_axis_wo_transmission(loop_seqs, uv_layer, + uv_min, width, height) + # align along to y-axis + else: + if self.transmission: + self.__align_to_y_axis_w_transmission(loop_seqs, uv_layer, + uv_min, width, height) + else: + self.__align_to_y_axis_wo_transmission(loop_seqs, uv_layer, + uv_min, width, height) + + def execute(self, context): + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + uv_layer = bm.loops.layers.uv.verify() + + # loop_seqs[horizontal][vertical][loop] + loop_seqs, error = common.get_loop_sequences(bm, uv_layer) + if not loop_seqs: + self.report({'WARNING'}, error) + return {'CANCELLED'} + + # get height and width + uv_max, uv_min = self.__get_uv_max_min(loop_seqs, uv_layer) + width = uv_max.x - uv_min.x + height = uv_max.y - uv_min.y + + self.__align(loop_seqs, uv_layer, uv_min, width, height) + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} diff --git a/uv_magic_uv/op/align_uv_cursor.py b/uv_magic_uv/op/align_uv_cursor.py new file mode 100644 index 00000000..cae1c89a --- /dev/null +++ b/uv_magic_uv/op/align_uv_cursor.py @@ -0,0 +1,154 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import bpy +from mathutils import Vector +from bpy.props import EnumProperty +import bmesh + +from .. import common + + +class MUV_AUVCAlignOps(bpy.types.Operator): + + bl_idname = "uv.muv_auvc_align" + bl_label = "Align" + bl_description = "Align cursor to the center of UV island" + bl_options = {'REGISTER', 'UNDO'} + + position = EnumProperty( + items=( + ('CENTER', "Center", "Align to Center"), + ('LEFT_TOP', "Left Top", "Align to Left Top"), + ('LEFT_MIDDLE', "Left Middle", "Align to Left Middle"), + ('LEFT_BOTTOM', "Left Bottom", "Align to Left Bottom"), + ('MIDDLE_TOP', "Middle Top", "Align to Middle Top"), + ('MIDDLE_BOTTOM', "Middle Bottom", "Align to Middle Bottom"), + ('RIGHT_TOP', "Right Top", "Align to Right Top"), + ('RIGHT_MIDDLE', "Right Middle", "Align to Right Middle"), + ('RIGHT_BOTTOM', "Right Bottom", "Align to Right Bottom") + ), + name="Position", + description="Align position", + default='CENTER' + ) + base = EnumProperty( + items=( + ('TEXTURE', "Texture", "Align based on Texture"), + ('UV', "UV", "Align to UV"), + ('UV_SEL', "UV (Selected)", "Align to Selected UV") + ), + name="Base", + description="Align base", + default='TEXTURE' + ) + + def execute(self, context): + area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW', + 'IMAGE_EDITOR') + bd_size = common.get_uvimg_editor_board_size(area) + + if self.base == 'UV': + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if not bm.loops.layers.uv: + return None + uv_layer = bm.loops.layers.uv.verify() + + max_ = Vector((-10000000.0, -10000000.0)) + min_ = Vector((10000000.0, 10000000.0)) + for f in bm.faces: + if not f.select: + continue + for l in f.loops: + uv = l[uv_layer].uv + max_.x = max(max_.x, uv.x) + max_.y = max(max_.y, uv.y) + min_.x = min(min_.x, uv.x) + min_.y = min(min_.y, uv.y) + center = Vector(((max_.x + min_.x) / 2.0, (max_.y + min_.y) / 2.0)) + + elif self.base == 'UV_SEL': + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if not bm.loops.layers.uv: + return None + uv_layer = bm.loops.layers.uv.verify() + + max_ = Vector((-10000000.0, -10000000.0)) + min_ = Vector((10000000.0, 10000000.0)) + for f in bm.faces: + if not f.select: + continue + for l in f.loops: + if not l[uv_layer].select: + continue + uv = l[uv_layer].uv + max_.x = max(max_.x, uv.x) + max_.y = max(max_.y, uv.y) + min_.x = min(min_.x, uv.x) + min_.y = min(min_.y, uv.y) + center = Vector(((max_.x + min_.x) / 2.0, (max_.y + min_.y) / 2.0)) + + elif self.base == 'TEXTURE': + min_ = Vector((0.0, 0.0)) + max_ = Vector((1.0, 1.0)) + center = Vector((0.5, 0.5)) + else: + self.report({'ERROR'}, "Unknown Operation") + + if self.position == 'CENTER': + cx = center.x * bd_size[0] + cy = center.y * bd_size[1] + elif self.position == 'LEFT_TOP': + cx = min_.x * bd_size[0] + cy = max_.y * bd_size[1] + elif self.position == 'LEFT_MIDDLE': + cx = min_.x * bd_size[0] + cy = center.y * bd_size[1] + elif self.position == 'LEFT_BOTTOM': + cx = min_.x * bd_size[0] + cy = min_.y * bd_size[1] + elif self.position == 'MIDDLE_TOP': + cx = center.x * bd_size[0] + cy = max_.y * bd_size[1] + elif self.position == 'MIDDLE_BOTTOM': + cx = center.x * bd_size[0] + cy = min_.y * bd_size[1] + elif self.position == 'RIGHT_TOP': + cx = max_.x * bd_size[0] + cy = max_.y * bd_size[1] + elif self.position == 'RIGHT_MIDDLE': + cx = max_.x * bd_size[0] + cy = center.y * bd_size[1] + elif self.position == 'RIGHT_BOTTOM': + cx = max_.x * bd_size[0] + cy = min_.y * bd_size[1] + else: + self.report({'ERROR'}, "Unknown Operation") + + space.cursor_location = Vector((cx, cy)) + + return {'FINISHED'} diff --git a/uv_magic_uv/muv_cpuv_ops.py b/uv_magic_uv/op/copy_paste_uv.py index 82f043c6..ee89b5e9 100644 --- a/uv_magic_uv/muv_cpuv_ops.py +++ b/uv_magic_uv/op/copy_paste_uv.py @@ -18,10 +18,13 @@ # # ##### END GPL LICENSE BLOCK ##### -__author__ = "Nutti <nutti.metro@gmail.com>, Jace Priester" +__author__ = "imdjs, Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import math +from math import atan2, sin, cos import bpy import bmesh @@ -31,16 +34,9 @@ from bpy.props import ( IntProperty, EnumProperty, ) -from . import muv_common - +from mathutils import Vector -def memorize_view_3d_mode(fn): - def __memorize_view_3d_mode(self, context): - mode_orig = bpy.context.object.mode - result = fn(self, context) - bpy.ops.object.mode_set(mode=mode_orig) - return result - return __memorize_view_3d_mode +from .. import common class MUV_CPUVCopyUV(bpy.types.Operator): @@ -64,7 +60,7 @@ class MUV_CPUVCopyUV(bpy.types.Operator): {'INFO'}, "Copy UV coordinate (UV map:%s)" % (self.uv_map)) obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() # get UV layer @@ -174,7 +170,7 @@ class MUV_CPUVPasteUV(bpy.types.Operator): {'INFO'}, "Paste UV coordinate (UV map:%s)" % (self.uv_map)) obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() # get UV layer @@ -273,46 +269,158 @@ class MUV_CPUVPasteUVMenu(bpy.types.Menu): bl_description = "Paste UV coordinate" def draw(self, context): + sc = context.scene layout = self.layout # create sub menu obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) uv_maps = bm.loops.layers.uv.keys() - layout.operator( - MUV_CPUVPasteUV.bl_idname, - text="[Default]", icon="IMAGE_COL").uv_map = "" + ops = layout.operator(MUV_CPUVPasteUV.bl_idname, text="[Default]") + ops.uv_map = "" + ops.copy_seams = sc.muv_cpuv_copy_seams + ops.strategy = sc.muv_cpuv_strategy for m in uv_maps: - layout.operator( - MUV_CPUVPasteUV.bl_idname, - text=m, icon="IMAGE_COL").uv_map = m + ops = layout.operator(MUV_CPUVPasteUV.bl_idname, text=m) + ops.uv_map = m + ops.copy_seams = sc.muv_cpuv_copy_seams + ops.strategy = sc.muv_cpuv_strategy -class MUV_CPUVObjCopyUV(bpy.types.Operator): +class MUV_CPUVIECopyUV(bpy.types.Operator): """ - Operation class: Copy UV coordinate per object + Operation class: Copy UV coordinate on UV/Image Editor """ - bl_idname = "object.muv_cpuv_obj_copy_uv" + bl_idname = "uv.muv_cpuv_ie_copy_uv" bl_label = "Copy UV" - bl_description = "Copy UV coordinate" + bl_description = "Copy UV coordinate (only selected in UV/Image Editor)" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return context.mode == 'EDIT_MESH' + + def execute(self, context): + props = context.scene.muv_props.cpuv + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + uv_layer = bm.loops.layers.uv.verify() + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + for face in bm.faces: + if not face.select: + continue + skip = False + for l in face.loops: + if not l[uv_layer].select: + skip = True + break + if skip: + continue + props.src_uvs.append([l[uv_layer].uv.copy() for l in face.loops]) + + return {'FINISHED'} + + +class MUV_CPUVIEPasteUV(bpy.types.Operator): + """ + Operation class: Paste UV coordinate on UV/Image Editor + """ + + bl_idname = "uv.muv_cpuv_ie_paste_uv" + bl_label = "Paste UV" + bl_description = "Paste UV coordinate (only selected in UV/Image Editor)" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return context.mode == 'EDIT_MESH' + + def execute(self, context): + props = context.scene.muv_props.cpuv + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + uv_layer = bm.loops.layers.uv.verify() + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + dest_uvs = [] + dest_face_indices = [] + for face in bm.faces: + if not face.select: + continue + skip = False + for l in face.loops: + if not l[uv_layer].select: + skip = True + break + if skip: + continue + dest_face_indices.append(face.index) + uvs = [l[uv_layer].uv.copy() for l in face.loops] + dest_uvs.append(uvs) + + for suvs, duvs in zip(props.src_uvs, dest_uvs): + src_diff = suvs[1] - suvs[0] + dest_diff = duvs[1] - duvs[0] + + src_base = suvs[0] + dest_base = duvs[0] + + src_rad = atan2(src_diff.y, src_diff.x) + dest_rad = atan2(dest_diff.y, dest_diff.x) + if src_rad < dest_rad: + radian = dest_rad - src_rad + elif src_rad > dest_rad: + radian = math.pi * 2 - (src_rad - dest_rad) + else: # src_rad == dest_rad + radian = 0.0 + + ratio = dest_diff.length / src_diff.length + break + + for suvs, fidx in zip(props.src_uvs, dest_face_indices): + for l, suv in zip(bm.faces[fidx].loops, suvs): + base = suv - src_base + radian_ref = atan2(base.y, base.x) + radian_fin = (radian + radian_ref) + length = base.length + turn = Vector((length * cos(radian_fin), + length * sin(radian_fin))) + target_uv = Vector((turn.x * ratio, turn.y * ratio)) + \ + dest_base + l[uv_layer].uv = target_uv + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} + + +class MUV_CPUVSelSeqCopyUV(bpy.types.Operator): + """ + Operation class: Copy UV coordinate by selection sequence + """ + + bl_idname = "uv.muv_cpuv_selseq_copy_uv" + bl_label = "Copy UV (Selection Sequence) (Operation)" + bl_description = "Copy UV data by selection sequence (Operation)" bl_options = {'REGISTER', 'UNDO'} uv_map = StringProperty(options={'HIDDEN'}) - @memorize_view_3d_mode def execute(self, context): - props = context.scene.muv_props.cpuv_obj + props = context.scene.muv_props.cpuv_selseq if self.uv_map == "": - self.report({'INFO'}, "Copy UV coordinate per object") + self.report({'INFO'}, "Copy UV coordinate (selection sequence)") else: self.report( {'INFO'}, - "Copy UV coordinate per object (UV map:%s)" % (self.uv_map)) - bpy.ops.object.mode_set(mode='EDIT') - + "Copy UV coordinate (selection sequence) (UV map:%s)" + % (self.uv_map)) obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() # get UV layer @@ -329,171 +437,210 @@ class MUV_CPUVObjCopyUV(bpy.types.Operator): props.src_uvs = [] props.src_pin_uvs = [] props.src_seams = [] - for face in bm.faces: - uvs = [l[uv_layer].uv.copy() for l in face.loops] - pin_uvs = [l[uv_layer].pin_uv for l in face.loops] - seams = [l.edge.seam for l in face.loops] - props.src_uvs.append(uvs) - props.src_pin_uvs.append(pin_uvs) - props.src_seams.append(seams) - - self.report({'INFO'}, "%s's UV coordinates are copied" % (obj.name)) + for hist in bm.select_history: + if isinstance(hist, bmesh.types.BMFace) and hist.select: + uvs = [l[uv_layer].uv.copy() for l in hist.loops] + pin_uvs = [l[uv_layer].pin_uv for l in hist.loops] + seams = [l.edge.seam for l in hist.loops] + props.src_uvs.append(uvs) + props.src_pin_uvs.append(pin_uvs) + props.src_seams.append(seams) + if not props.src_uvs or not props.src_pin_uvs: + self.report({'WARNING'}, "No faces are selected") + return {'CANCELLED'} + self.report({'INFO'}, "%d face(s) are selected" % len(props.src_uvs)) return {'FINISHED'} -class MUV_CPUVObjCopyUVMenu(bpy.types.Menu): +class MUV_CPUVSelSeqCopyUVMenu(bpy.types.Menu): """ - Menu class: Copy UV coordinate per object + Menu class: Copy UV coordinate by selection sequence """ - bl_idname = "object.muv_cpuv_obj_copy_uv_menu" - bl_label = "Copy UV" - bl_description = "Copy UV coordinate per object" + bl_idname = "uv.muv_cpuv_selseq_copy_uv_menu" + bl_label = "Copy UV (Selection Sequence)" + bl_description = "Copy UV coordinate by selection sequence" - def draw(self, _): + def draw(self, context): layout = self.layout - # create sub menu - uv_maps = bpy.context.active_object.data.uv_textures.keys() + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + uv_maps = bm.loops.layers.uv.keys() layout.operator( - MUV_CPUVObjCopyUV.bl_idname, + MUV_CPUVSelSeqCopyUV.bl_idname, text="[Default]", icon="IMAGE_COL").uv_map = "" for m in uv_maps: layout.operator( - MUV_CPUVObjCopyUV.bl_idname, + MUV_CPUVSelSeqCopyUV.bl_idname, text=m, icon="IMAGE_COL").uv_map = m -class MUV_CPUVObjPasteUV(bpy.types.Operator): +class MUV_CPUVSelSeqPasteUV(bpy.types.Operator): """ - Operation class: Paste UV coordinate per object + Operation class: Paste UV coordinate by selection sequence """ - bl_idname = "object.muv_cpuv_obj_paste_uv" - bl_label = "Paste UV" - bl_description = "Paste UV coordinate" + bl_idname = "uv.muv_cpuv_selseq_paste_uv" + bl_label = "Paste UV (Selection Sequence) (Operation)" + bl_description = "Paste UV coordinate by selection sequence (Operation)" bl_options = {'REGISTER', 'UNDO'} uv_map = StringProperty(options={'HIDDEN'}) + strategy = EnumProperty( + name="Strategy", + description="Paste Strategy", + items=[ + ('N_N', 'N:N', 'Number of faces must be equal to source'), + ('N_M', 'N:M', 'Number of faces must not be equal to source') + ], + default="N_M" + ) + flip_copied_uv = BoolProperty( + name="Flip Copied UV", + description="Flip Copied UV...", + default=False + ) + rotate_copied_uv = IntProperty( + default=0, + name="Rotate Copied UV", + min=0, + max=30 + ) copy_seams = BoolProperty( name="Copy Seams", description="Copy Seams", default=True ) - @memorize_view_3d_mode def execute(self, context): - props = context.scene.muv_props.cpuv_obj + props = context.scene.muv_props.cpuv_selseq if not props.src_uvs or not props.src_pin_uvs: self.report({'WARNING'}, "Need copy UV at first") return {'CANCELLED'} + if self.uv_map == "": + self.report({'INFO'}, "Paste UV coordinate (selection sequence)") + else: + self.report( + {'INFO'}, + "Paste UV coordinate (selection sequence) (UV map:%s)" + % (self.uv_map)) - for o in bpy.data.objects: - if not hasattr(o.data, "uv_textures") or not o.select: - continue - - bpy.ops.object.mode_set(mode='OBJECT') - bpy.context.scene.objects.active = o - bpy.ops.object.mode_set(mode='EDIT') - - obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: - bm.faces.ensure_lookup_table() + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() - if (self.uv_map == "" or - self.uv_map not in bm.loops.layers.uv.keys()): - self.report({'INFO'}, "Paste UV coordinate per object") - else: + # get UV layer + if self.uv_map == "": + if not bm.loops.layers.uv: self.report( - {'INFO'}, - "Paste UV coordinate per object (UV map: %s)" - % (self.uv_map)) - - # get UV layer - if (self.uv_map == "" or - self.uv_map not in bm.loops.layers.uv.keys()): - if not bm.loops.layers.uv: - self.report( - {'WARNING'}, "Object must have more than one UV map") - return {'CANCELLED'} - uv_layer = bm.loops.layers.uv.verify() - else: - uv_layer = bm.loops.layers.uv[self.uv_map] - - # get selected face - dest_uvs = [] - dest_pin_uvs = [] - dest_seams = [] - dest_face_indices = [] - for face in bm.faces: - dest_face_indices.append(face.index) - uvs = [l[uv_layer].uv.copy() for l in face.loops] - pin_uvs = [l[uv_layer].pin_uv for l in face.loops] - seams = [l.edge.seam for l in face.loops] + {'WARNING'}, "Object must have more than one UV map") + return {'CANCELLED'} + uv_layer = bm.loops.layers.uv.verify() + else: + uv_layer = bm.loops.layers.uv[self.uv_map] + + # get selected face + dest_uvs = [] + dest_pin_uvs = [] + dest_seams = [] + dest_face_indices = [] + for hist in bm.select_history: + if isinstance(hist, bmesh.types.BMFace) and hist.select: + dest_face_indices.append(hist.index) + uvs = [l[uv_layer].uv.copy() for l in hist.loops] + pin_uvs = [l[uv_layer].pin_uv for l in hist.loops] + seams = [l.edge.seam for l in hist.loops] dest_uvs.append(uvs) dest_pin_uvs.append(pin_uvs) dest_seams.append(seams) - if len(props.src_uvs) != len(dest_uvs): - self.report( - {'WARNING'}, - "Number of faces is different from copied " + - "(src:%d, dest:%d)" - % (len(props.src_uvs), len(dest_uvs)) - ) - return {'CANCELLED'} + if not dest_uvs or not dest_pin_uvs: + self.report({'WARNING'}, "No faces are selected") + return {'CANCELLED'} + if self.strategy == 'N_N' and len(props.src_uvs) != len(dest_uvs): + self.report( + {'WARNING'}, + "Number of selected faces is different from copied faces " + + "(src:%d, dest:%d)" + % (len(props.src_uvs), len(dest_uvs))) + return {'CANCELLED'} - # paste - for i, idx in enumerate(dest_face_indices): + # paste + for i, idx in enumerate(dest_face_indices): + suv = None + spuv = None + ss = None + duv = None + if self.strategy == 'N_N': suv = props.src_uvs[i] spuv = props.src_pin_uvs[i] ss = props.src_seams[i] duv = dest_uvs[i] - if len(suv) != len(duv): - self.report({'WARNING'}, "Some faces are different size") - return {'CANCELLED'} - suvs_fr = [uv for uv in suv] - spuvs_fr = [pin_uv for pin_uv in spuv] - ss_fr = [s for s in ss] - # paste UVs - for l, suv, spuv, ss in zip( - bm.faces[idx].loops, suvs_fr, spuvs_fr, ss_fr): - l[uv_layer].uv = suv - l[uv_layer].pin_uv = spuv - if self.copy_seams is True: - l.edge.seam = ss - - bmesh.update_edit_mesh(obj.data) - if self.copy_seams is True: - obj.data.show_edge_seams = True + elif self.strategy == 'N_M': + suv = props.src_uvs[i % len(props.src_uvs)] + spuv = props.src_pin_uvs[i % len(props.src_pin_uvs)] + ss = props.src_seams[i % len(props.src_seams)] + duv = dest_uvs[i] + if len(suv) != len(duv): + self.report({'WARNING'}, "Some faces are different size") + return {'CANCELLED'} + suvs_fr = [uv for uv in suv] + spuvs_fr = [pin_uv for pin_uv in spuv] + ss_fr = [s for s in ss] + # flip UVs + if self.flip_copied_uv is True: + suvs_fr.reverse() + spuvs_fr.reverse() + ss_fr.reverse() + # rotate UVs + for _ in range(self.rotate_copied_uv): + uv = suvs_fr.pop() + pin_uv = spuvs_fr.pop() + s = ss_fr.pop() + suvs_fr.insert(0, uv) + spuvs_fr.insert(0, pin_uv) + ss_fr.insert(0, s) + # paste UVs + for l, suv, spuv, ss in zip(bm.faces[idx].loops, suvs_fr, + spuvs_fr, ss_fr): + l[uv_layer].uv = suv + l[uv_layer].pin_uv = spuv + if self.copy_seams is True: + l.edge.seam = ss - self.report( - {'INFO'}, "%s's UV coordinates are pasted" % (obj.name)) + self.report({'INFO'}, "%d face(s) are copied" % len(dest_uvs)) + + bmesh.update_edit_mesh(obj.data) + if self.copy_seams is True: + obj.data.show_edge_seams = True return {'FINISHED'} -class MUV_CPUVObjPasteUVMenu(bpy.types.Menu): +class MUV_CPUVSelSeqPasteUVMenu(bpy.types.Menu): """ - Menu class: Paste UV coordinate per object + Menu class: Paste UV coordinate by selection sequence """ - bl_idname = "object.muv_cpuv_obj_paste_uv_menu" - bl_label = "Paste UV" - bl_description = "Paste UV coordinate per object" + bl_idname = "uv.muv_cpuv_selseq_paste_uv_menu" + bl_label = "Paste UV (Selection Sequence)" + bl_description = "Paste UV coordinate by selection sequence" - def draw(self, _): + def draw(self, context): + sc = context.scene layout = self.layout # create sub menu - uv_maps = [] - for obj in bpy.data.objects: - if hasattr(obj.data, "uv_textures") and obj.select: - uv_maps.extend(obj.data.uv_textures.keys()) - uv_maps = list(set(uv_maps)) - layout.operator( - MUV_CPUVObjPasteUV.bl_idname, - text="[Default]", icon="IMAGE_COL").uv_map = "" + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + uv_maps = bm.loops.layers.uv.keys() + ops = layout.operator(MUV_CPUVSelSeqPasteUV.bl_idname, + text="[Default]") + ops.uv_map = "" + ops.copy_seams = sc.muv_cpuv_copy_seams + ops.strategy = sc.muv_cpuv_strategy for m in uv_maps: - layout.operator( - MUV_CPUVObjPasteUV.bl_idname, - text=m, icon="IMAGE_COL").uv_map = m + ops = layout.operator(MUV_CPUVSelSeqPasteUV.bl_idname, text=m) + ops.uv_map = m + ops.copy_seams = sc.muv_cpuv_copy_seams + ops.strategy = sc.muv_cpuv_strategy diff --git a/uv_magic_uv/op/copy_paste_uv_object.py b/uv_magic_uv/op/copy_paste_uv_object.py new file mode 100644 index 00000000..d80ee415 --- /dev/null +++ b/uv_magic_uv/op/copy_paste_uv_object.py @@ -0,0 +1,252 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import bpy +import bmesh +from bpy.props import ( + StringProperty, + BoolProperty, +) + +from .. import common + + +def memorize_view_3d_mode(fn): + def __memorize_view_3d_mode(self, context): + mode_orig = bpy.context.object.mode + result = fn(self, context) + bpy.ops.object.mode_set(mode=mode_orig) + return result + return __memorize_view_3d_mode + + +class MUV_CPUVObjCopyUV(bpy.types.Operator): + """ + Operation class: Copy UV coordinate per object + """ + + bl_idname = "object.muv_cpuv_obj_copy_uv" + bl_label = "Copy UV" + bl_description = "Copy UV coordinate" + bl_options = {'REGISTER', 'UNDO'} + + uv_map = StringProperty(options={'HIDDEN'}) + + @memorize_view_3d_mode + def execute(self, context): + props = context.scene.muv_props.cpuv_obj + if self.uv_map == "": + self.report({'INFO'}, "Copy UV coordinate per object") + else: + self.report( + {'INFO'}, + "Copy UV coordinate per object (UV map:%s)" % (self.uv_map)) + bpy.ops.object.mode_set(mode='EDIT') + + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + # get UV layer + if self.uv_map == "": + if not bm.loops.layers.uv: + self.report( + {'WARNING'}, "Object must have more than one UV map") + return {'CANCELLED'} + uv_layer = bm.loops.layers.uv.verify() + else: + uv_layer = bm.loops.layers.uv[self.uv_map] + + # get selected face + props.src_uvs = [] + props.src_pin_uvs = [] + props.src_seams = [] + for face in bm.faces: + uvs = [l[uv_layer].uv.copy() for l in face.loops] + pin_uvs = [l[uv_layer].pin_uv for l in face.loops] + seams = [l.edge.seam for l in face.loops] + props.src_uvs.append(uvs) + props.src_pin_uvs.append(pin_uvs) + props.src_seams.append(seams) + + self.report({'INFO'}, "%s's UV coordinates are copied" % (obj.name)) + + return {'FINISHED'} + + +class MUV_CPUVObjCopyUVMenu(bpy.types.Menu): + """ + Menu class: Copy UV coordinate per object + """ + + bl_idname = "object.muv_cpuv_obj_copy_uv_menu" + bl_label = "Copy UV" + bl_description = "Copy UV coordinate per object" + + def draw(self, _): + layout = self.layout + # create sub menu + uv_maps = bpy.context.active_object.data.uv_textures.keys() + layout.operator(MUV_CPUVObjCopyUV.bl_idname, text="[Default]")\ + .uv_map = "" + for m in uv_maps: + layout.operator(MUV_CPUVObjCopyUV.bl_idname, text=m).uv_map = m + + +class MUV_CPUVObjPasteUV(bpy.types.Operator): + """ + Operation class: Paste UV coordinate per object + """ + + bl_idname = "object.muv_cpuv_obj_paste_uv" + bl_label = "Paste UV" + bl_description = "Paste UV coordinate" + bl_options = {'REGISTER', 'UNDO'} + + uv_map = StringProperty(options={'HIDDEN'}) + copy_seams = BoolProperty( + name="Copy Seams", + description="Copy Seams", + default=True + ) + + @memorize_view_3d_mode + def execute(self, context): + props = context.scene.muv_props.cpuv_obj + if not props.src_uvs or not props.src_pin_uvs: + self.report({'WARNING'}, "Need copy UV at first") + return {'CANCELLED'} + + for o in bpy.data.objects: + if not hasattr(o.data, "uv_textures") or not o.select: + continue + + bpy.ops.object.mode_set(mode='OBJECT') + bpy.context.scene.objects.active = o + bpy.ops.object.mode_set(mode='EDIT') + + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + if (self.uv_map == "" or + self.uv_map not in bm.loops.layers.uv.keys()): + self.report({'INFO'}, "Paste UV coordinate per object") + else: + self.report( + {'INFO'}, + "Paste UV coordinate per object (UV map: %s)" + % (self.uv_map)) + + # get UV layer + if (self.uv_map == "" or + self.uv_map not in bm.loops.layers.uv.keys()): + if not bm.loops.layers.uv: + self.report( + {'WARNING'}, "Object must have more than one UV map") + return {'CANCELLED'} + uv_layer = bm.loops.layers.uv.verify() + else: + uv_layer = bm.loops.layers.uv[self.uv_map] + + # get selected face + dest_uvs = [] + dest_pin_uvs = [] + dest_seams = [] + dest_face_indices = [] + for face in bm.faces: + dest_face_indices.append(face.index) + uvs = [l[uv_layer].uv.copy() for l in face.loops] + pin_uvs = [l[uv_layer].pin_uv for l in face.loops] + seams = [l.edge.seam for l in face.loops] + dest_uvs.append(uvs) + dest_pin_uvs.append(pin_uvs) + dest_seams.append(seams) + if len(props.src_uvs) != len(dest_uvs): + self.report( + {'WARNING'}, + "Number of faces is different from copied " + + "(src:%d, dest:%d)" + % (len(props.src_uvs), len(dest_uvs)) + ) + return {'CANCELLED'} + + # paste + for i, idx in enumerate(dest_face_indices): + suv = props.src_uvs[i] + spuv = props.src_pin_uvs[i] + ss = props.src_seams[i] + duv = dest_uvs[i] + if len(suv) != len(duv): + self.report({'WARNING'}, "Some faces are different size") + return {'CANCELLED'} + suvs_fr = [uv for uv in suv] + spuvs_fr = [pin_uv for pin_uv in spuv] + ss_fr = [s for s in ss] + # paste UVs + for l, suv, spuv, ss in zip( + bm.faces[idx].loops, suvs_fr, spuvs_fr, ss_fr): + l[uv_layer].uv = suv + l[uv_layer].pin_uv = spuv + if self.copy_seams is True: + l.edge.seam = ss + + bmesh.update_edit_mesh(obj.data) + if self.copy_seams is True: + obj.data.show_edge_seams = True + + self.report( + {'INFO'}, "%s's UV coordinates are pasted" % (obj.name)) + + return {'FINISHED'} + + +class MUV_CPUVObjPasteUVMenu(bpy.types.Menu): + """ + Menu class: Paste UV coordinate per object + """ + + bl_idname = "object.muv_cpuv_obj_paste_uv_menu" + bl_label = "Paste UV" + bl_description = "Paste UV coordinate per object" + + def draw(self, context): + sc = context.scene + layout = self.layout + # create sub menu + uv_maps = [] + for obj in bpy.data.objects: + if hasattr(obj.data, "uv_textures") and obj.select: + uv_maps.extend(obj.data.uv_textures.keys()) + uv_maps = list(set(uv_maps)) + ops = layout.operator(MUV_CPUVObjPasteUV.bl_idname, text="[Default]") + ops.uv_map = "" + ops.copy_seams = sc.muv_cpuv_copy_seams + for m in uv_maps: + ops = layout.operator(MUV_CPUVObjPasteUV.bl_idname, text=m) + ops.uv_map = m + ops.copy_seams = sc.muv_cpuv_copy_seams diff --git a/uv_magic_uv/op/copy_paste_uv_uvedit.py b/uv_magic_uv/op/copy_paste_uv_uvedit.py new file mode 100644 index 00000000..96908020 --- /dev/null +++ b/uv_magic_uv/op/copy_paste_uv_uvedit.py @@ -0,0 +1,144 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>, Jace Priester" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import math +from math import atan2, sin, cos + +import bpy +import bmesh +from mathutils import Vector + +from .. import common + + +class MUV_CPUVIECopyUV(bpy.types.Operator): + """ + Operation class: Copy UV coordinate on UV/Image Editor + """ + + bl_idname = "uv.muv_cpuv_ie_copy_uv" + bl_label = "Copy UV" + bl_description = "Copy UV coordinate (only selected in UV/Image Editor)" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return context.mode == 'EDIT_MESH' + + def execute(self, context): + props = context.scene.muv_props.cpuv + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + uv_layer = bm.loops.layers.uv.verify() + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + for face in bm.faces: + if not face.select: + continue + skip = False + for l in face.loops: + if not l[uv_layer].select: + skip = True + break + if skip: + continue + props.src_uvs.append([l[uv_layer].uv.copy() for l in face.loops]) + + return {'FINISHED'} + + +class MUV_CPUVIEPasteUV(bpy.types.Operator): + """ + Operation class: Paste UV coordinate on UV/Image Editor + """ + + bl_idname = "uv.muv_cpuv_ie_paste_uv" + bl_label = "Paste UV" + bl_description = "Paste UV coordinate (only selected in UV/Image Editor)" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return context.mode == 'EDIT_MESH' + + def execute(self, context): + props = context.scene.muv_props.cpuv + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + uv_layer = bm.loops.layers.uv.verify() + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + dest_uvs = [] + dest_face_indices = [] + for face in bm.faces: + if not face.select: + continue + skip = False + for l in face.loops: + if not l[uv_layer].select: + skip = True + break + if skip: + continue + dest_face_indices.append(face.index) + uvs = [l[uv_layer].uv.copy() for l in face.loops] + dest_uvs.append(uvs) + + for suvs, duvs in zip(props.src_uvs, dest_uvs): + src_diff = suvs[1] - suvs[0] + dest_diff = duvs[1] - duvs[0] + + src_base = suvs[0] + dest_base = duvs[0] + + src_rad = atan2(src_diff.y, src_diff.x) + dest_rad = atan2(dest_diff.y, dest_diff.x) + if src_rad < dest_rad: + radian = dest_rad - src_rad + elif src_rad > dest_rad: + radian = math.pi * 2 - (src_rad - dest_rad) + else: # src_rad == dest_rad + radian = 0.0 + + ratio = dest_diff.length / src_diff.length + break + + for suvs, fidx in zip(props.src_uvs, dest_face_indices): + for l, suv in zip(bm.faces[fidx].loops, suvs): + base = suv - src_base + radian_ref = atan2(base.y, base.x) + radian_fin = (radian + radian_ref) + length = base.length + turn = Vector((length * cos(radian_fin), + length * sin(radian_fin))) + target_uv = Vector((turn.x * ratio, turn.y * ratio)) + \ + dest_base + l[uv_layer].uv = target_uv + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} diff --git a/uv_magic_uv/muv_fliprot_ops.py b/uv_magic_uv/op/flip_rotate_uv.py index 334eb14c..30f6b0f7 100644 --- a/uv_magic_uv/muv_fliprot_ops.py +++ b/uv_magic_uv/op/flip_rotate_uv.py @@ -20,8 +20,8 @@ __author__ = "Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" +__version__ = "5.1" +__date__ = "24 Feb 2018" import bpy import bmesh @@ -29,7 +29,8 @@ from bpy.props import ( BoolProperty, IntProperty, ) -from . import muv_common + +from .. import common class MUV_FlipRot(bpy.types.Operator): @@ -63,7 +64,7 @@ class MUV_FlipRot(bpy.types.Operator): self.report({'INFO'}, "Flip/Rotate UV") obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() # get UV layer diff --git a/uv_magic_uv/muv_mirroruv_ops.py b/uv_magic_uv/op/mirror_uv.py index 63eb9bd5..f4849d18 100644 --- a/uv_magic_uv/muv_mirroruv_ops.py +++ b/uv_magic_uv/op/mirror_uv.py @@ -20,8 +20,8 @@ __author__ = "Keith (Wahooney) Boshoff, Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" +__version__ = "5.1" +__date__ = "24 Feb 2018" import bpy from bpy.props import ( @@ -30,7 +30,8 @@ from bpy.props import ( ) import bmesh from mathutils import Vector -from . import muv_common + +from .. import common class MUV_MirrorUV(bpy.types.Operator): @@ -113,7 +114,7 @@ class MUV_MirrorUV(bpy.types.Operator): error = self.error axis = self.axis - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() if not bm.loops.layers.uv: self.report({'WARNING'}, "Object must have more than one UV map") diff --git a/uv_magic_uv/muv_mvuv_ops.py b/uv_magic_uv/op/move_uv.py index 28346270..6382376c 100644 --- a/uv_magic_uv/muv_mvuv_ops.py +++ b/uv_magic_uv/op/move_uv.py @@ -20,8 +20,8 @@ __author__ = "kgeogeo, mem, Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" +__version__ = "5.1" +__date__ = "24 Feb 2018" import bpy import bmesh @@ -64,6 +64,7 @@ class MUV_MVUV(bpy.types.Operator): return context.edit_object def modal(self, context, event): + props = context.scene.muv_props.mvuv if self.__first_time is True: self.__prev_mouse = Vector(( event.mouse_region_x, event.mouse_region_y)) @@ -84,7 +85,7 @@ class MUV_MVUV(bpy.types.Operator): event.mouse_region_x, event.mouse_region_y)) # check if operation is started - if self.__running is True: + if self.__running: if event.type == 'LEFTMOUSE' and event.value == 'RELEASE': self.__running = False return {'RUNNING_MODAL'} @@ -110,16 +111,20 @@ class MUV_MVUV(bpy.types.Operator): if event.type == cancel_btn and event.value == 'PRESS': for (fidx, vidx), uv in zip(self.__topology_dict, self.__ini_uvs): bm.faces[fidx].loops[vidx][active_uv].uv = uv + props.running = False return {'FINISHED'} # confirmed if event.type == confirm_btn and event.value == 'PRESS': + props.running = False return {'FINISHED'} return {'RUNNING_MODAL'} def execute(self, context): - self.__first_time = True + props = context.scene.muv_props.mvuv + props.running = True self.__running = True + self.__first_time = True context.window_manager.modal_handler_add(self) self.__topology_dict, self.__ini_uvs = self.__find_uv(context) return {'RUNNING_MODAL'} diff --git a/uv_magic_uv/muv_packuv_ops.py b/uv_magic_uv/op/pack_uv.py index f663e662..a780af3e 100644 --- a/uv_magic_uv/muv_packuv_ops.py +++ b/uv_magic_uv/op/pack_uv.py @@ -20,11 +20,10 @@ __author__ = "Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" +__version__ = "5.1" +__date__ = "24 Feb 2018" from math import fabs -from collections import defaultdict import bpy import bmesh @@ -36,7 +35,7 @@ from bpy.props import ( ) from mathutils import Vector -from . import muv_common +from .. import common class MUV_PackUV(bpy.types.Operator): @@ -69,23 +68,21 @@ class MUV_PackUV(bpy.types.Operator): min=0.000001, max=0.1, default=(0.001, 0.001), - size=2) + size=2 + ) allowable_size_deviation = FloatVectorProperty( name="Allowable Size Deviation", description="Allowable sizse deviation to judge same UV island", min=0.000001, max=0.1, default=(0.001, 0.001), - size=2) + size=2 + ) - def __init__(self): - self.__face_to_verts = defaultdict(set) - self.__vert_to_faces = defaultdict(set) - - def execute(self, _): - obj = bpy.context.active_object + def execute(self, context): + obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() if not bm.loops.layers.uv: self.report({'WARNING'}, "Object must have more than one UV map") @@ -93,17 +90,7 @@ class MUV_PackUV(bpy.types.Operator): uv_layer = bm.loops.layers.uv.verify() selected_faces = [f for f in bm.faces if f.select] - - # create mesh database - for f in selected_faces: - for l in f.loops: - id_ = l[uv_layer].uv.to_tuple(5), l.vert.index - self.__face_to_verts[f.index].add(id_) - self.__vert_to_faces[id_].add(f.index) - - # Group island - uv_island_lists = self.__get_island(bm) - island_info = self.__get_island_info(uv_layer, uv_island_lists) + island_info = common.get_island_info(obj) num_group = self.__group_island(island_info) loop_lists = [l for f in bm.faces for l in f.loops] @@ -183,13 +170,17 @@ class MUV_PackUV(bpy.types.Operator): dsx = isl_2['size'].x - isl_1['size'].x dsy = isl_2['size'].y - isl_1['size'].y center_x_matched = ( - fabs(dcx) < self.allowable_center_deviation[0]) + fabs(dcx) < self.allowable_center_deviation[0] + ) center_y_matched = ( - fabs(dcy) < self.allowable_center_deviation[1]) + fabs(dcy) < self.allowable_center_deviation[1] + ) size_x_matched = ( - fabs(dsx) < self.allowable_size_deviation[0]) + fabs(dsx) < self.allowable_size_deviation[0] + ) size_y_matched = ( - fabs(dsy) < self.allowable_size_deviation[1]) + fabs(dsy) < self.allowable_size_deviation[1] + ) center_matched = center_x_matched and center_y_matched size_matched = size_x_matched and size_y_matched num_uv_matched = (isl_2['num_uv'] == isl_1['num_uv']) @@ -214,75 +205,3 @@ class MUV_PackUV(bpy.types.Operator): num_group = num_group + 1 return num_group - - def __get_island_info(self, uv_layer, islands): - """ - get information about each island - """ - - island_info = [] - for isl in islands: - info = {} - max_uv = Vector((-10000000.0, -10000000.0)) - min_uv = Vector((10000000.0, 10000000.0)) - ave_uv = Vector((0.0, 0.0)) - num_uv = 0 - for face in isl: - n = 0 - a = Vector((0.0, 0.0)) - for l in face['face'].loops: - uv = l[uv_layer].uv - if uv.x > max_uv.x: - max_uv.x = uv.x - if uv.y > max_uv.y: - max_uv.y = uv.y - if uv.x < min_uv.x: - min_uv.x = uv.x - if uv.y < min_uv.y: - min_uv.y = uv.y - a = a + uv - n = n + 1 - ave_uv = ave_uv + a - num_uv = num_uv + n - a = a / n - face['ave_uv'] = a - ave_uv = ave_uv / num_uv - - info['center'] = ave_uv - info['size'] = max_uv - min_uv - info['num_uv'] = num_uv - info['group'] = -1 - info['faces'] = isl - - island_info.append(info) - - return island_info - - def __parse_island(self, bm, face_idx, faces_left, island): - """ - Parse island - """ - - if face_idx in faces_left: - faces_left.remove(face_idx) - island.append({'face': bm.faces[face_idx]}) - for v in self.__face_to_verts[face_idx]: - connected_faces = self.__vert_to_faces[v] - if connected_faces: - for cf in connected_faces: - self.__parse_island(bm, cf, faces_left, island) - - def __get_island(self, bm): - """ - Get island list - """ - - uv_island_lists = [] - faces_left = set(self.__face_to_verts.keys()) - while faces_left: - current_island = [] - face_idx = list(faces_left)[0] - self.__parse_island(bm, face_idx, faces_left, current_island) - uv_island_lists.append(current_island) - - return uv_island_lists diff --git a/uv_magic_uv/muv_preserve_uv_aspect.py b/uv_magic_uv/op/preserve_uv_aspect.py index 68e75f74..bc2f1b81 100644 --- a/uv_magic_uv/muv_preserve_uv_aspect.py +++ b/uv_magic_uv/op/preserve_uv_aspect.py @@ -20,14 +20,15 @@ __author__ = "Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" +__version__ = "5.1" +__date__ = "24 Feb 2018" import bpy import bmesh from bpy.props import StringProperty, EnumProperty from mathutils import Vector -from . import muv_common + +from .. import common class MUV_PreserveUVAspect(bpy.types.Operator): @@ -71,7 +72,7 @@ class MUV_PreserveUVAspect(bpy.types.Operator): obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() if not bm.loops.layers.uv: @@ -202,22 +203,3 @@ class MUV_PreserveUVAspect(bpy.types.Operator): bmesh.update_edit_mesh(obj.data) return {'FINISHED'} - - -class MUV_PreserveUVAspectMenu(bpy.types.Menu): - """ - Menu class: Preserve UV Aspect - """ - - bl_idname = "uv.muv_preserve_uv_aspect_menu" - bl_label = "Preserve UV Aspect" - bl_description = "Preserve UV Aspect" - - def draw(self, _): - layout = self.layout - - # create sub menu - for key in bpy.data.images.keys(): - layout.operator( - MUV_PreserveUVAspect.bl_idname, - text=key, icon="IMAGE_COL").dest_img_name = key diff --git a/uv_magic_uv/op/smooth_uv.py b/uv_magic_uv/op/smooth_uv.py new file mode 100644 index 00000000..aa9b22c0 --- /dev/null +++ b/uv_magic_uv/op/smooth_uv.py @@ -0,0 +1,215 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "imdjs, Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import bpy +import bmesh +from bpy.props import BoolProperty, FloatProperty + +from .. import common + + +class MUV_AUVSmooth(bpy.types.Operator): + + bl_idname = "uv.muv_auv_smooth" + bl_label = "Smooth" + bl_description = "Smooth UV coordinates" + bl_options = {'REGISTER', 'UNDO'} + + transmission = BoolProperty( + name="Transmission", + description="Smooth linked UVs", + default=False + ) + mesh_infl = FloatProperty( + name="Mesh Influence", + description="Influence rate of mesh vertex", + min=0.0, + max=1.0, + default=0.0 + ) + select = BoolProperty( + name="Select", + description="Select UVs which are smoothed", + default=False + ) + + @classmethod + def poll(cls, context): + return context.mode == 'EDIT_MESH' + + def __smooth_wo_transmission(self, loop_seqs, uv_layer): + # calculate path length + loops = [] + for hseq in loop_seqs: + loops.extend([hseq[0][0], hseq[0][1]]) + full_vlen = 0 + accm_vlens = [0.0] + full_uvlen = 0 + accm_uvlens = [0.0] + orig_uvs = [loop_seqs[0][0][0][uv_layer].uv.copy()] + for l1, l2 in zip(loops[:-1], loops[1:]): + diff_v = l2.vert.co - l1.vert.co + full_vlen = full_vlen + diff_v.length + accm_vlens.append(full_vlen) + diff_uv = l2[uv_layer].uv - l1[uv_layer].uv + full_uvlen = full_uvlen + diff_uv.length + accm_uvlens.append(full_uvlen) + orig_uvs.append(l2[uv_layer].uv.copy()) + + for hidx, hseq in enumerate(loop_seqs): + pair = hseq[0] + for pidx, l in enumerate(pair): + if self.select: + l[uv_layer].select = True + + # ignore start/end loop + if (hidx == 0 and pidx == 0) or\ + ((hidx == len(loop_seqs) - 1) and (pidx == len(pair) - 1)): + continue + + # calculate target path length + # target = no influenced * (1 - infl) + influenced * infl + tgt_noinfl = full_uvlen * (hidx + pidx) / (len(loop_seqs)) + tgt_infl = full_uvlen * accm_vlens[hidx * 2 + pidx] / full_vlen + target_length = tgt_noinfl * (1 - self.mesh_infl) + \ + tgt_infl * self.mesh_infl + + # get target UV + for i in range(len(accm_uvlens[:-1])): + # get line segment which UV will be placed + if ((accm_uvlens[i] <= target_length) and + (accm_uvlens[i + 1] > target_length)): + tgt_seg_len = target_length - accm_uvlens[i] + seg_len = accm_uvlens[i + 1] - accm_uvlens[i] + uv1 = orig_uvs[i] + uv2 = orig_uvs[i + 1] + target_uv = uv1 + (uv2 - uv1) * tgt_seg_len / seg_len + break + else: + self.report({'ERROR'}, "Failed to get target UV") + return {'CANCELLED'} + + # update UV + l[uv_layer].uv = target_uv + + def __smooth_w_transmission(self, loop_seqs, uv_layer): + # calculate path length + loops = [] + for vidx in range(len(loop_seqs[0])): + ls = [] + for hseq in loop_seqs: + ls.extend(hseq[vidx]) + loops.append(ls) + + orig_uvs = [] + accm_vlens = [] + full_vlens = [] + accm_uvlens = [] + full_uvlens = [] + for ls in loops: + full_v = 0.0 + accm_v = [0.0] + full_uv = 0.0 + accm_uv = [0.0] + uvs = [ls[0][uv_layer].uv.copy()] + for l1, l2 in zip(ls[:-1], ls[1:]): + diff_v = l2.vert.co - l1.vert.co + full_v = full_v + diff_v.length + accm_v.append(full_v) + diff_uv = l2[uv_layer].uv - l1[uv_layer].uv + full_uv = full_uv + diff_uv.length + accm_uv.append(full_uv) + uvs.append(l2[uv_layer].uv.copy()) + accm_vlens.append(accm_v) + full_vlens.append(full_v) + accm_uvlens.append(accm_uv) + full_uvlens.append(full_uv) + orig_uvs.append(uvs) + + for hidx, hseq in enumerate(loop_seqs): + for vidx, (pair, uvs, accm_v, full_v, accm_uv, full_uv)\ + in enumerate(zip(hseq, orig_uvs, accm_vlens, full_vlens, + accm_uvlens, full_uvlens)): + for pidx, l in enumerate(pair): + if self.select: + l[uv_layer].select = True + + # ignore start/end loop + if hidx == 0 and pidx == 0: + continue + if hidx == len(loop_seqs) - 1 and pidx == len(pair) - 1: + continue + + # calculate target path length + # target = no influenced * (1 - infl) + influenced * infl + tgt_noinfl = full_uv * (hidx + pidx) / (len(loop_seqs)) + tgt_infl = full_uv * accm_v[hidx * 2 + pidx] / full_v + target_length = tgt_noinfl * (1 - self.mesh_infl) + \ + tgt_infl * self.mesh_infl + + # get target UV + for i in range(len(accm_uv[:-1])): + # get line segment to be placed + if ((accm_uv[i] <= target_length) and + (accm_uv[i + 1] > target_length)): + tgt_seg_len = target_length - accm_uv[i] + seg_len = accm_uv[i + 1] - accm_uv[i] + uv1 = uvs[i] + uv2 = uvs[i + 1] + target_uv = uv1 +\ + (uv2 - uv1) * tgt_seg_len / seg_len + break + else: + self.report({'ERROR'}, "Failed to get target UV") + return {'CANCELLED'} + + # update UV + l[uv_layer].uv = target_uv + + def __smooth(self, loop_seqs, uv_layer): + if self.transmission: + self.__smooth_w_transmission(loop_seqs, uv_layer) + else: + self.__smooth_wo_transmission(loop_seqs, uv_layer) + + def execute(self, context): + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + uv_layer = bm.loops.layers.uv.verify() + + # loop_seqs[horizontal][vertical][loop] + loop_seqs, error = common.get_loop_sequences(bm, uv_layer) + if not loop_seqs: + self.report({'WARNING'}, error) + return {'CANCELLED'} + + # smooth + self.__smooth(loop_seqs, uv_layer) + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} diff --git a/uv_magic_uv/muv_texlock_ops.py b/uv_magic_uv/op/texture_lock.py index bfc95129..d6c56f5a 100644 --- a/uv_magic_uv/muv_texlock_ops.py +++ b/uv_magic_uv/op/texture_lock.py @@ -20,20 +20,18 @@ __author__ = "Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" +__version__ = "5.1" +__date__ = "24 Feb 2018" import math -from math import ( - atan2, cos, - sqrt, sin, fabs, -) +from math import atan2, cos, sqrt, sin, fabs import bpy import bmesh from mathutils import Vector from bpy.props import BoolProperty -from . import muv_common + +from .. import common def get_vco(verts_orig, loop): @@ -195,7 +193,7 @@ class MUV_TexLockStart(bpy.types.Operator): props = context.scene.muv_props.texlock obj = bpy.context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.verts.ensure_lookup_table() bm.edges.ensure_lookup_table() bm.faces.ensure_lookup_table() @@ -224,13 +222,15 @@ class MUV_TexLockStop(bpy.types.Operator): connect = BoolProperty( name="Connect UV", - default=True) + default=True + ) def execute(self, context): - props = context.scene.muv_props.texlock + sc = context.scene + props = sc.muv_props.texlock obj = bpy.context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.verts.ensure_lookup_table() bm.edges.ensure_lookup_table() bm.faces.ensure_lookup_table() @@ -297,14 +297,14 @@ class MUV_TexLockUpdater(bpy.types.Operator): props = context.scene.muv_props.texlock obj = bpy.context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.verts.ensure_lookup_table() bm.edges.ensure_lookup_table() bm.faces.ensure_lookup_table() if not bm.loops.layers.uv: self.report({'WARNING'}, "Object must have more than one UV map") - return {'CANCELLED'} + return uv_layer = bm.loops.layers.uv.verify() verts = [v.index for v in bm.verts if v.select] @@ -313,7 +313,7 @@ class MUV_TexLockUpdater(bpy.types.Operator): for vidx, v_orig in zip(verts, verts_orig): if vidx != v_orig["vidx"]: self.report({'ERROR'}, "Internal Error") - return {"CANCELLED"} + return v = bm.verts[vidx] link_loops = get_link_loops(v) @@ -336,7 +336,7 @@ class MUV_TexLockUpdater(bpy.types.Operator): v_orig["moved"] = True bmesh.update_edit_mesh(obj.data) - muv_common.redraw_all_areas() + common.redraw_all_areas() props.intr_verts_orig = [ {"vidx": v.index, "vco": v.co.copy(), "moved": False} for v in bm.verts if v.select] @@ -395,7 +395,7 @@ class MUV_TexLockIntrStart(bpy.types.Operator): obj = bpy.context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.verts.ensure_lookup_table() bm.edges.ensure_lookup_table() bm.faces.ensure_lookup_table() diff --git a/uv_magic_uv/muv_texproj_ops.py b/uv_magic_uv/op/texture_projection.py index ffa4e789..77a81aa0 100644 --- a/uv_magic_uv/muv_texproj_ops.py +++ b/uv_magic_uv/op/texture_projection.py @@ -20,8 +20,8 @@ __author__ = "Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" +__version__ = "5.1" +__date__ = "24 Feb 2018" from collections import namedtuple @@ -31,7 +31,7 @@ import bmesh import mathutils from bpy_extras import view3d_utils -from . import muv_common +from .. import common Rect = namedtuple('Rect', 'x0 y0 x1 y1') @@ -237,28 +237,28 @@ class MUV_TexProjProject(bpy.types.Operator): def execute(self, context): sc = context.scene - if context.mode != "EDIT_MESH": - self.report({'WARNING'}, "Mesh must be in Edit mode") - return {'CANCELLED'} - if sc.muv_texproj_tex_image == "None": self.report({'WARNING'}, "No textures are selected") return {'CANCELLED'} - _, region, space = muv_common.get_space( + _, region, space = common.get_space( 'VIEW_3D', 'WINDOW', 'VIEW_3D') # get faces to be texture projected obj = context.active_object world_mat = obj.matrix_world bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() # get UV and texture layer if not bm.loops.layers.uv: - self.report({'WARNING'}, "Object must have more than one UV map") - return {'CANCELLED'} + if sc.muv_texproj_assign_uvmap: + bm.loops.layers.uv.new() + else: + self.report({'WARNING'}, + "Object must have more than one UV map") + return {'CANCELLED'} uv_layer = bm.loops.layers.uv.verify() tex_layer = bm.faces.layers.tex.verify() @@ -290,50 +290,7 @@ class MUV_TexProjProject(bpy.types.Operator): l[uv_layer].uv = v_canvas[i].to_2d() i = i + 1 - muv_common.redraw_all_areas() + common.redraw_all_areas() bmesh.update_edit_mesh(obj.data) return {'FINISHED'} - - -class OBJECT_PT_TP(bpy.types.Panel): - """ - Panel class: Texture Projection Menu on Property Panel on View3D - """ - - bl_label = "Texture Projection" - bl_description = "Texture Projection Menu" - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' - bl_context = 'mesh_edit' - - @classmethod - def poll(cls, context): - prefs = context.user_preferences.addons["uv_magic_uv"].preferences - return prefs.enable_texproj - - def draw_header(self, _): - layout = self.layout - layout.label(text="", icon='IMAGE_COL') - - def draw(self, context): - sc = context.scene - layout = self.layout - props = sc.muv_props.texproj - if props.running is False: - layout.operator( - MUV_TexProjStart.bl_idname, text="Start", icon='PLAY') - else: - layout.operator( - MUV_TexProjStop.bl_idname, text="Stop", icon='PAUSE') - layout.prop(sc, "muv_texproj_tex_image", text="Image") - layout.prop( - sc, "muv_texproj_tex_transparency", text="Transparency" - ) - layout.prop(sc, "muv_texproj_adjust_window", text="Adjust Window") - if not sc.muv_texproj_adjust_window: - layout.prop(sc, "muv_texproj_tex_magnitude", text="Magnitude") - layout.prop( - sc, "muv_texproj_apply_tex_aspect", text="Texture Aspect Ratio" - ) - layout.operator(MUV_TexProjProject.bl_idname, text="Project") diff --git a/uv_magic_uv/op/texture_wrap.py b/uv_magic_uv/op/texture_wrap.py new file mode 100644 index 00000000..01e507bd --- /dev/null +++ b/uv_magic_uv/op/texture_wrap.py @@ -0,0 +1,212 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import bpy +import bmesh + +from .. import common + + +class MUV_TexWrapRefer(bpy.types.Operator): + """ + Operation class: Refer UV + """ + + bl_idname = "uv.muv_texwrap_refer" + bl_label = "Refer" + bl_description = "Refer UV" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + props = context.scene.muv_props.texwrap + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + if not bm.loops.layers.uv: + self.report({'WARNING'}, "Object must have more than one UV map") + return {'CANCELLED'} + + sel_faces = [f for f in bm.faces if f.select] + if len(sel_faces) != 1: + self.report({'WARNING'}, "Must select only one face") + return {'CANCELLED'} + + props.ref_face_index = sel_faces[0].index + props.ref_obj = obj + + return {'FINISHED'} + + +class MUV_TexWrapSet(bpy.types.Operator): + """ + Operation class: Set UV + """ + + bl_idname = "uv.muv_texwrap_set" + bl_label = "Set" + bl_description = "Set UV" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + sc = context.scene + props = sc.muv_props.texwrap + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + + if not bm.loops.layers.uv: + self.report({'WARNING'}, "Object must have more than one UV map") + return {'CANCELLED'} + uv_layer = bm.loops.layers.uv.verify() + + if sc.muv_texwrap_selseq: + sel_faces = [] + for hist in bm.select_history: + if isinstance(hist, bmesh.types.BMFace) and hist.select: + sel_faces.append(hist) + if not sel_faces: + self.report({'WARNING'}, "Must select more than one face") + return {'CANCELLED'} + else: + sel_faces = [f for f in bm.faces if f.select] + if len(sel_faces) != 1: + self.report({'WARNING'}, "Must select only one face") + return {'CANCELLED'} + + ref_face_index = props.ref_face_index + for face in sel_faces: + tgt_face_index = face.index + if ref_face_index == tgt_face_index: + self.report({'WARNING'}, "Must select different face") + return {'CANCELLED'} + + if props.ref_obj != obj: + self.report({'WARNING'}, "Object must be same") + return {'CANCELLED'} + + ref_face = bm.faces[ref_face_index] + tgt_face = bm.faces[tgt_face_index] + + # get common vertices info + common_verts = [] + for sl in ref_face.loops: + for dl in tgt_face.loops: + if sl.vert == dl.vert: + info = {"vert": sl.vert, "ref_loop": sl, + "tgt_loop": dl} + common_verts.append(info) + break + + if len(common_verts) != 2: + self.report({'WARNING'}, + "2 verticies must be shared among faces") + return {'CANCELLED'} + + # get reference other vertices info + ref_other_verts = [] + for sl in ref_face.loops: + for ci in common_verts: + if sl.vert == ci["vert"]: + break + else: + info = {"vert": sl.vert, "loop": sl} + ref_other_verts.append(info) + + if not ref_other_verts: + self.report({'WARNING'}, "More than 1 vertex must be unshared") + return {'CANCELLED'} + + # get reference info + ref_info = {} + cv0 = common_verts[0]["vert"].co + cv1 = common_verts[1]["vert"].co + cuv0 = common_verts[0]["ref_loop"][uv_layer].uv + cuv1 = common_verts[1]["ref_loop"][uv_layer].uv + ov0 = ref_other_verts[0]["vert"].co + ouv0 = ref_other_verts[0]["loop"][uv_layer].uv + ref_info["vert_vdiff"] = cv1 - cv0 + ref_info["uv_vdiff"] = cuv1 - cuv0 + ref_info["vert_hdiff"], _ = common.diff_point_to_segment( + cv0, cv1, ov0) + ref_info["uv_hdiff"], _ = common.diff_point_to_segment( + cuv0, cuv1, ouv0) + + # get target other vertices info + tgt_other_verts = [] + for dl in tgt_face.loops: + for ci in common_verts: + if dl.vert == ci["vert"]: + break + else: + info = {"vert": dl.vert, "loop": dl} + tgt_other_verts.append(info) + + if not tgt_other_verts: + self.report({'WARNING'}, "More than 1 vertex must be unshared") + return {'CANCELLED'} + + # get target info + for info in tgt_other_verts: + cv0 = common_verts[0]["vert"].co + cv1 = common_verts[1]["vert"].co + cuv0 = common_verts[0]["ref_loop"][uv_layer].uv + ov = info["vert"].co + info["vert_hdiff"], x = common.diff_point_to_segment( + cv0, cv1, ov) + info["vert_vdiff"] = x - common_verts[0]["vert"].co + + # calclulate factor + fact_h = -info["vert_hdiff"].length / \ + ref_info["vert_hdiff"].length + fact_v = info["vert_vdiff"].length / \ + ref_info["vert_vdiff"].length + duv_h = ref_info["uv_hdiff"] * fact_h + duv_v = ref_info["uv_vdiff"] * fact_v + + # get target UV + info["target_uv"] = cuv0 + duv_h + duv_v + + # apply to common UVs + for info in common_verts: + info["tgt_loop"][uv_layer].uv = \ + info["ref_loop"][uv_layer].uv.copy() + # apply to other UVs + for info in tgt_other_verts: + info["loop"][uv_layer].uv = info["target_uv"] + + common.debug_print("===== Target Other Verticies =====") + common.debug_print(tgt_other_verts) + + bmesh.update_edit_mesh(obj.data) + + ref_face_index = tgt_face_index + + if sc.muv_texwrap_set_and_refer: + props.ref_face_index = tgt_face_index + + return {'FINISHED'} diff --git a/uv_magic_uv/muv_transuv_ops.py b/uv_magic_uv/op/transfer_uv.py index ed0a3c46..132f395e 100644 --- a/uv_magic_uv/muv_transuv_ops.py +++ b/uv_magic_uv/op/transfer_uv.py @@ -20,8 +20,8 @@ __author__ = "Nutti <nutti.metro@gmail.com>, Mifth, MaxRobinot" __status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" +__version__ = "5.1" +__date__ = "24 Feb 2018" from collections import OrderedDict @@ -29,8 +29,7 @@ import bpy import bmesh from bpy.props import BoolProperty -from . import muv_props -from . import muv_common +from .. import common class MUV_TransUVCopy(bpy.types.Operator): @@ -48,7 +47,7 @@ class MUV_TransUVCopy(bpy.types.Operator): props = context.scene.muv_props.transuv active_obj = context.scene.objects.active bm = bmesh.from_edit_mesh(active_obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() # get UV layer @@ -115,7 +114,7 @@ class MUV_TransUVPaste(bpy.types.Operator): props = context.scene.muv_props.transuv active_obj = context.scene.objects.active bm = bmesh.from_edit_mesh(active_obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() # get UV layer @@ -291,19 +290,19 @@ def parse_faces( vert1 = sorted_edge.verts[0] vert2 = sorted_edge.verts[1] - muv_common.debug_print(face_stuff[0], vert1, vert2) + common.debug_print(face_stuff[0], vert1, vert2) if face_stuff[0].index(vert1) > face_stuff[0].index(vert2): vert1 = sorted_edge.verts[1] vert2 = sorted_edge.verts[0] - muv_common.debug_print(shared_face.verts, vert1, vert2) + common.debug_print(shared_face.verts, vert1, vert2) new_face_stuff = get_other_verts_edges( shared_face, vert1, vert2, sorted_edge, uv_layer) all_sorted_faces[shared_face] = new_face_stuff used_verts.update(shared_face.verts) used_edges.update(shared_face.edges) - if muv_props.DEBUG: + if common.DEBUG: shared_face.select = True # test which faces are parsed new_shared_faces.append(shared_face) diff --git a/uv_magic_uv/muv_unwrapconst_ops.py b/uv_magic_uv/op/unwrap_constraint.py index 1a691119..e98879b7 100644 --- a/uv_magic_uv/muv_unwrapconst_ops.py +++ b/uv_magic_uv/op/unwrap_constraint.py @@ -18,8 +18,8 @@ __author__ = "Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" +__version__ = "5.1" +__date__ = "24 Feb 2018" import bpy import bmesh @@ -28,7 +28,8 @@ from bpy.props import ( EnumProperty, FloatProperty, ) -from . import muv_common + +from .. import common class MUV_UnwrapConstraint(bpy.types.Operator): @@ -74,18 +75,21 @@ class MUV_UnwrapConstraint(bpy.types.Operator): u_const = BoolProperty( name="U-Constraint", description="Keep UV U-axis coordinate", - default=False) + default=False + ) v_const = BoolProperty( name="V-Constraint", description="Keep UV V-axis coordinate", - default=False) + default=False + ) def execute(self, _): obj = bpy.context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() + # bpy.ops.uv.unwrap() makes one UV map at least if not bm.loops.layers.uv: self.report({'WARNING'}, "Object must have more than one UV map") return {'CANCELLED'} diff --git a/uv_magic_uv/muv_uvbb_ops.py b/uv_magic_uv/op/uv_bounding_box.py index 4f7b0631..9ebc76c4 100644 --- a/uv_magic_uv/muv_uvbb_ops.py +++ b/uv_magic_uv/op/uv_bounding_box.py @@ -20,8 +20,8 @@ __author__ = "Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" +__version__ = "5.1" +__date__ = "24 Feb 2018" from enum import IntEnum import math @@ -31,7 +31,7 @@ import bgl import mathutils import bmesh -from . import muv_common +from .. import common MAX_VALUE = 100000.0 @@ -602,17 +602,23 @@ class MUV_UVBBUpdater(bpy.types.Operator): """ Get UV coordinate """ + sc = context.scene obj = context.active_object uv_info = [] bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() if not bm.loops.layers.uv: return None uv_layer = bm.loops.layers.uv.verify() for f in bm.faces: - if f.select: - for i, l in enumerate(f.loops): + if not f.select: + continue + for i, l in enumerate(f.loops): + if sc.muv_uvbb_boundary == 'UV_SEL': + if l[uv_layer].select: + uv_info.append((f.index, i, l[uv_layer].uv.copy())) + elif sc.muv_uvbb_boundary == 'UV': uv_info.append((f.index, i, l[uv_layer].uv.copy())) if not uv_info: return None @@ -661,7 +667,7 @@ class MUV_UVBBUpdater(bpy.types.Operator): """ obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() if not bm.loops.layers.uv: return @@ -683,10 +689,17 @@ class MUV_UVBBUpdater(bpy.types.Operator): def modal(self, context, event): props = context.scene.muv_props.uvbb - muv_common.redraw_all_areas() + common.redraw_all_areas() if props.running is False: self.__handle_remove(context) return {'FINISHED'} + + area, _, _ = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D') + + if event.mouse_region_x < 0 or event.mouse_region_x > area.width or \ + event.mouse_region_y < 0 or event.mouse_region_y > area.height: + return {'PASS_THROUGH'} + if event.type == 'TIMER': trans_mat = self.__cmd_exec.execute() self.__update_uvs(context, props.uv_info_ini, trans_mat) @@ -695,7 +708,7 @@ class MUV_UVBBUpdater(bpy.types.Operator): self.__state_mgr.update(context, props.ctrl_points, event) - return {'PASS_THROUGH'} + return {'RUNNING_MODAL'} def execute(self, context): props = context.scene.muv_props.uvbb @@ -717,37 +730,3 @@ class MUV_UVBBUpdater(bpy.types.Operator): props.running = True return {'RUNNING_MODAL'} - - -class IMAGE_PT_MUV_UVBB(bpy.types.Panel): - """ - Panel class: UV Bounding Box Menu on Property Panel on UV/ImageEditor - """ - - bl_space_type = 'IMAGE_EDITOR' - bl_region_type = 'UI' - bl_label = "UV Bounding Box" - bl_context = 'mesh_edit' - - @classmethod - def poll(cls, context): - prefs = context.user_preferences.addons["uv_magic_uv"].preferences - return prefs.enable_uvbb - - def draw_header(self, _): - layout = self.layout - layout.label(text="", icon='IMAGE_COL') - - def draw(self, context): - sc = context.scene - props = sc.muv_props.uvbb - layout = self.layout - if props.running is False: - layout.operator( - MUV_UVBBUpdater.bl_idname, text="Display UV Bounding Box", - icon='PLAY') - else: - layout.operator( - MUV_UVBBUpdater.bl_idname, text="Hide UV Bounding Box", - icon='PAUSE') - layout.prop(sc, "muv_uvbb_uniform_scaling", text="Uniform Scaling") diff --git a/uv_magic_uv/op/uv_inspection.py b/uv_magic_uv/op/uv_inspection.py new file mode 100644 index 00000000..60a754a3 --- /dev/null +++ b/uv_magic_uv/op/uv_inspection.py @@ -0,0 +1,623 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import bpy +import bmesh +import bgl +from mathutils import Vector + +from .. import common + + +def is_polygon_same(points1, points2): + if len(points1) != len(points2): + return False + + pts1 = points1.as_list() + pts2 = points2.as_list() + + for p1 in pts1: + for p2 in pts2: + diff = p2 - p1 + if diff.length < 0.0000001: + pts2.remove(p2) + break + else: + return False + + return True + + +def is_segment_intersect(start1, end1, start2, end2): + seg1 = end1 - start1 + seg2 = end2 - start2 + + a1 = -seg1.y + b1 = seg1.x + d1 = -(a1 * start1.x + b1 * start1.y) + + a2 = -seg2.y + b2 = seg2.x + d2 = -(a2 * start2.x + b2 * start2.y) + + seg1_line2_start = a2 * start1.x + b2 * start1.y + d2 + seg1_line2_end = a2 * end1.x + b2 * end1.y + d2 + + seg2_line1_start = a1 * start2.x + b1 * start2.y + d1 + seg2_line1_end = a1 * end2.x + b1 * end2.y + d1 + + if (seg1_line2_start * seg1_line2_end >= 0) or \ + (seg2_line1_start * seg2_line1_end >= 0): + return False, None + + u = seg1_line2_start / (seg1_line2_start - seg1_line2_end) + out = start1 + u * seg1 + + return True, out + + +class RingBuffer: + def __init__(self, arr): + self.__buffer = arr.copy() + self.__pointer = 0 + + def __repr__(self): + return repr(self.__buffer) + + def __len__(self): + return len(self.__buffer) + + def insert(self, val, offset=0): + self.__buffer.insert(self.__pointer + offset, val) + + def head(self): + return self.__buffer[0] + + def tail(self): + return self.__buffer[-1] + + def get(self, offset=0): + size = len(self.__buffer) + val = self.__buffer[(self.__pointer + offset) % size] + return val + + def next(self): + size = len(self.__buffer) + self.__pointer = (self.__pointer + 1) % size + + def reset(self): + self.__pointer = 0 + + def find(self, obj): + try: + idx = self.__buffer.index(obj) + except ValueError: + return None + return self.__buffer[idx] + + def find_and_next(self, obj): + size = len(self.__buffer) + idx = self.__buffer.index(obj) + self.__pointer = (idx + 1) % size + + def find_and_set(self, obj): + idx = self.__buffer.index(obj) + self.__pointer = idx + + def as_list(self): + return self.__buffer.copy() + + def reverse(self): + self.__buffer.reverse() + self.reset() + + +# clip: reference polygon +# subject: tested polygon +def do_weiler_atherton_cliping(clip, subject, uv_layer, mode): + + clip_uvs = RingBuffer([l[uv_layer].uv.copy() for l in clip.loops]) + if is_polygon_flipped(clip_uvs): + clip_uvs.reverse() + subject_uvs = RingBuffer([l[uv_layer].uv.copy() for l in subject.loops]) + if is_polygon_flipped(subject_uvs): + subject_uvs.reverse() + + common.debug_print("===== Clip UV List =====") + common.debug_print(clip_uvs) + common.debug_print("===== Subject UV List =====") + common.debug_print(subject_uvs) + + # check if clip and subject is overlapped completely + if is_polygon_same(clip_uvs, subject_uvs): + polygons = [subject_uvs.as_list()] + common.debug_print("===== Polygons Overlapped Completely =====") + common.debug_print(polygons) + return True, polygons + + # check if subject is in clip + if is_points_in_polygon(subject_uvs, clip_uvs): + polygons = [subject_uvs.as_list()] + return True, polygons + + # check if clip is in subject + if is_points_in_polygon(clip_uvs, subject_uvs): + polygons = [subject_uvs.as_list()] + return True, polygons + + # check if clip and subject is overlapped partially + intersections = [] + while True: + subject_uvs.reset() + while True: + uv_start1 = clip_uvs.get() + uv_end1 = clip_uvs.get(1) + uv_start2 = subject_uvs.get() + uv_end2 = subject_uvs.get(1) + intersected, point = is_segment_intersect(uv_start1, uv_end1, + uv_start2, uv_end2) + if intersected: + clip_uvs.insert(point, 1) + subject_uvs.insert(point, 1) + intersections.append([point, + [clip_uvs.get(), clip_uvs.get(1)]]) + subject_uvs.next() + if subject_uvs.get() == subject_uvs.head(): + break + clip_uvs.next() + if clip_uvs.get() == clip_uvs.head(): + break + + common.debug_print("===== Intersection List =====") + common.debug_print(intersections) + + # no intersection, so subject and clip is not overlapped + if not intersections: + return False, None + + def get_intersection_pair(intersections, key): + for sect in intersections: + if sect[0] == key: + return sect[1] + + return None + + # make enter/exit pair + subject_uvs.reset() + subject_entering = [] + subject_exiting = [] + clip_entering = [] + clip_exiting = [] + intersect_uv_list = [] + while True: + pair = get_intersection_pair(intersections, subject_uvs.get()) + if pair: + sub = subject_uvs.get(1) - subject_uvs.get(-1) + inter = pair[1] - pair[0] + cross = sub.x * inter.y - inter.x * sub.y + if cross < 0: + subject_entering.append(subject_uvs.get()) + clip_exiting.append(subject_uvs.get()) + else: + subject_exiting.append(subject_uvs.get()) + clip_entering.append(subject_uvs.get()) + intersect_uv_list.append(subject_uvs.get()) + + subject_uvs.next() + if subject_uvs.get() == subject_uvs.head(): + break + + common.debug_print("===== Enter List =====") + common.debug_print(clip_entering) + common.debug_print(subject_entering) + common.debug_print("===== Exit List =====") + common.debug_print(clip_exiting) + common.debug_print(subject_exiting) + + # for now, can't handle the situation when fulfill all below conditions + # * two faces have common edge + # * each face is intersected + # * Show Mode is "Part" + # so for now, ignore this situation + if len(subject_entering) != len(subject_exiting): + if mode == 'FACE': + polygons = [subject_uvs.as_list()] + return True, polygons + return False, None + + def traverse(current_list, entering, exiting, poly, current, other_list): + result = current_list.find(current) + if not result: + return None + if result != current: + print("Internal Error") + return None + + # enter + if entering.count(current) >= 1: + entering.remove(current) + + current_list.find_and_next(current) + current = current_list.get() + + while exiting.count(current) == 0: + poly.append(current.copy()) + current_list.find_and_next(current) + current = current_list.get() + + # exit + poly.append(current.copy()) + exiting.remove(current) + + other_list.find_and_set(current) + return other_list.get() + + # Traverse + polygons = [] + current_uv_list = subject_uvs + other_uv_list = clip_uvs + current_entering = subject_entering + current_exiting = subject_exiting + + poly = [] + current_uv = current_entering[0] + + while True: + current_uv = traverse(current_uv_list, current_entering, + current_exiting, poly, current_uv, other_uv_list) + + if current_uv_list == subject_uvs: + current_uv_list = clip_uvs + other_uv_list = subject_uvs + current_entering = clip_entering + current_exiting = clip_exiting + common.debug_print("-- Next: Clip --") + else: + current_uv_list = subject_uvs + other_uv_list = clip_uvs + current_entering = subject_entering + current_exiting = subject_exiting + common.debug_print("-- Next: Subject --") + + common.debug_print(clip_entering) + common.debug_print(clip_exiting) + common.debug_print(subject_entering) + common.debug_print(subject_exiting) + + if not clip_entering and not clip_exiting \ + and not subject_entering and not subject_exiting: + break + + polygons.append(poly) + + common.debug_print("===== Polygons Overlapped Partially =====") + common.debug_print(polygons) + + return True, polygons + + +class MUV_UVInspRenderer(bpy.types.Operator): + """ + Operation class: Render UV Inspection + No operation (only rendering) + """ + + bl_idname = "uv.muv_uvinsp_renderer" + bl_description = "Render overlapped/flipped UVs" + bl_label = "Overlapped/Flipped UV renderer" + + __handle = None + + @staticmethod + def handle_add(obj, context): + sie = bpy.types.SpaceImageEditor + MUV_UVInspRenderer.__handle = sie.draw_handler_add( + MUV_UVInspRenderer.draw, (obj, context), 'WINDOW', 'POST_PIXEL') + + @staticmethod + def handle_remove(): + if MUV_UVInspRenderer.__handle is not None: + bpy.types.SpaceImageEditor.draw_handler_remove( + MUV_UVInspRenderer.__handle, 'WINDOW') + MUV_UVInspRenderer.__handle = None + + @staticmethod + def draw(_, context): + sc = context.scene + props = sc.muv_props.uvinsp + prefs = context.user_preferences.addons["uv_magic_uv"].preferences + + # OpenGL configuration + bgl.glEnable(bgl.GL_BLEND) + + # render overlapped UV + if sc.muv_uvinsp_show_overlapped: + color = prefs.uvinsp_overlapped_color + for info in props.overlapped_info: + if sc.muv_uvinsp_show_mode == 'PART': + for poly in info["polygons"]: + bgl.glBegin(bgl.GL_TRIANGLE_FAN) + bgl.glColor4f(color[0], color[1], color[2], color[3]) + for uv in poly: + x, y = context.region.view2d.view_to_region( + uv.x, uv.y) + bgl.glVertex2f(x, y) + bgl.glEnd() + elif sc.muv_uvinsp_show_mode == 'FACE': + bgl.glBegin(bgl.GL_TRIANGLE_FAN) + bgl.glColor4f(color[0], color[1], color[2], color[3]) + for uv in info["subject_uvs"]: + x, y = context.region.view2d.view_to_region(uv.x, uv.y) + bgl.glVertex2f(x, y) + bgl.glEnd() + + # render flipped UV + if sc.muv_uvinsp_show_flipped: + color = prefs.uvinsp_flipped_color + for info in props.flipped_info: + if sc.muv_uvinsp_show_mode == 'PART': + for poly in info["polygons"]: + bgl.glBegin(bgl.GL_TRIANGLE_FAN) + bgl.glColor4f(color[0], color[1], color[2], color[3]) + for uv in poly: + x, y = context.region.view2d.view_to_region( + uv.x, uv.y) + bgl.glVertex2f(x, y) + bgl.glEnd() + elif sc.muv_uvinsp_show_mode == 'FACE': + bgl.glBegin(bgl.GL_TRIANGLE_FAN) + bgl.glColor4f(color[0], color[1], color[2], color[3]) + for uv in info["uvs"]: + x, y = context.region.view2d.view_to_region(uv.x, uv.y) + bgl.glVertex2f(x, y) + bgl.glEnd() + + +def is_polygon_flipped(points): + area = 0.0 + for i in range(len(points)): + uv1 = points.get(i) + uv2 = points.get(i + 1) + a = uv1.x * uv2.y - uv1.y * uv2.x + area = area + a + if area < 0: + # clock-wise + return True + return False + + +def is_point_in_polygon(point, subject_points): + count = 0 + for i in range(len(subject_points)): + uv_start1 = subject_points.get(i) + uv_end1 = subject_points.get(i + 1) + uv_start2 = point + uv_end2 = Vector((1000000.0, point.y)) + intersected, _ = is_segment_intersect(uv_start1, uv_end1, + uv_start2, uv_end2) + if intersected: + count = count + 1 + + return count % 2 + + +def is_points_in_polygon(points, subject_points): + for i in range(len(points)): + internal = is_point_in_polygon(points.get(i), subject_points) + if not internal: + return False + + return True + + +def get_overlapped_uv_info(bm, faces, uv_layer, mode): + # at first, check island overlapped + isl = common.get_island_info_from_faces(bm, faces, uv_layer) + overlapped_isl_pairs = [] + for i, i1 in enumerate(isl): + for i2 in isl[i + 1:]: + if (i1["max"].x < i2["min"].x) or (i2["max"].x < i1["min"].x) or \ + (i1["max"].y < i2["min"].y) or (i2["max"].y < i1["min"].y): + continue + overlapped_isl_pairs.append([i1, i2]) + + # next, check polygon overlapped + overlapped_uvs = [] + for oip in overlapped_isl_pairs: + for clip in oip[0]["faces"]: + f_clip = clip["face"] + for subject in oip[1]["faces"]: + f_subject = subject["face"] + + # fast operation, apply bounding box algorithm + if (clip["max_uv"].x < subject["min_uv"].x) or \ + (subject["max_uv"].x < clip["min_uv"].x) or \ + (clip["max_uv"].y < subject["min_uv"].y) or \ + (subject["max_uv"].y < clip["min_uv"].y): + continue + + # slow operation, apply Weiler-Atherton cliping algorithm + result, polygons = do_weiler_atherton_cliping(f_clip, + f_subject, + uv_layer, mode) + if result: + subject_uvs = [l[uv_layer].uv.copy() + for l in f_subject.loops] + overlapped_uvs.append({"clip_face": f_clip, + "subject_face": f_subject, + "subject_uvs": subject_uvs, + "polygons": polygons}) + + return overlapped_uvs + + +def get_flipped_uv_info(faces, uv_layer): + flipped_uvs = [] + for f in faces: + polygon = RingBuffer([l[uv_layer].uv.copy() for l in f.loops]) + if is_polygon_flipped(polygon): + uvs = [l[uv_layer].uv.copy() for l in f.loops] + flipped_uvs.append({"face": f, "uvs": uvs, + "polygons": [polygon.as_list()]}) + + return flipped_uvs + + +def update_uvinsp_info(context): + sc = context.scene + props = sc.muv_props.uvinsp + + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + uv_layer = bm.loops.layers.uv.verify() + + if context.tool_settings.use_uv_select_sync: + sel_faces = [f for f in bm.faces] + else: + sel_faces = [f for f in bm.faces if f.select] + props.overlapped_info = get_overlapped_uv_info(bm, sel_faces, uv_layer, + sc.muv_uvinsp_show_mode) + props.flipped_info = get_flipped_uv_info(sel_faces, uv_layer) + + +class MUV_UVInspUpdate(bpy.types.Operator): + """ + Operation class: Update + """ + + bl_idname = "uv.muv_uvinsp_update" + bl_label = "Update" + bl_description = "Update Overlapped/Flipped UV" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + update_uvinsp_info(context) + + if context.area: + context.area.tag_redraw() + + return {'FINISHED'} + + +class MUV_UVInspDisplay(bpy.types.Operator): + """ + Operation class: Display + """ + + bl_idname = "uv.muv_uvinsp_display" + bl_label = "Display" + bl_description = "Display Overlapped/Flipped UV" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + sc = context.scene + props = sc.muv_props.uvinsp + if not props.display_running: + update_uvinsp_info(context) + MUV_UVInspRenderer.handle_add(self, context) + props.display_running = True + else: + MUV_UVInspRenderer.handle_remove() + props.display_running = False + + if context.area: + context.area.tag_redraw() + + return {'FINISHED'} + + +class MUV_UVInspSelectOverlapped(bpy.types.Operator): + """ + Operation class: Select faces which have overlapped UVs + """ + + bl_idname = "uv.muv_uvinsp_select_overlapped" + bl_label = "Overlapped" + bl_description = "Select faces which have overlapped UVs" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + uv_layer = bm.loops.layers.uv.verify() + + if context.tool_settings.use_uv_select_sync: + sel_faces = [f for f in bm.faces] + else: + sel_faces = [f for f in bm.faces if f.select] + + overlapped_info = get_overlapped_uv_info(bm, sel_faces, uv_layer, + 'FACE') + + for info in overlapped_info: + if context.tool_settings.use_uv_select_sync: + info["subject_face"].select = True + else: + for l in info["subject_face"].loops: + l[uv_layer].select = True + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} + + +class MUV_UVInspSelectFlipped(bpy.types.Operator): + """ + Operation class: Select faces which have flipped UVs + """ + + bl_idname = "uv.muv_uvinsp_select_flipped" + bl_label = "Flipped" + bl_description = "Select faces which have flipped UVs" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + uv_layer = bm.loops.layers.uv.verify() + + if context.tool_settings.use_uv_select_sync: + sel_faces = [f for f in bm.faces] + else: + sel_faces = [f for f in bm.faces if f.select] + + flipped_info = get_flipped_uv_info(sel_faces, uv_layer) + + for info in flipped_info: + if context.tool_settings.use_uv_select_sync: + info["face"].select = True + else: + for l in info["face"].loops: + l[uv_layer].select = True + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} diff --git a/uv_magic_uv/op/uv_sculpt.py b/uv_magic_uv/op/uv_sculpt.py new file mode 100644 index 00000000..2bf76abd --- /dev/null +++ b/uv_magic_uv/op/uv_sculpt.py @@ -0,0 +1,360 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +from math import pi, cos, tan, sin + +import bpy +import bmesh +import bgl +from mathutils import Vector +from bpy_extras import view3d_utils +from mathutils.bvhtree import BVHTree +from mathutils.geometry import barycentric_transform + +from .. import common + + +class MUV_UVSculptRenderer(bpy.types.Operator): + """ + Operation class: Render Brush + """ + + bl_idname = "uv.muv_uvsculpt_renderer" + bl_label = "Brush Renderer" + bl_description = "Brush Renderer in View3D" + + __handle = None + + @staticmethod + def handle_add(obj, context): + if MUV_UVSculptRenderer.__handle is None: + sv = bpy.types.SpaceView3D + MUV_UVSculptRenderer.__handle = sv.draw_handler_add( + MUV_UVSculptRenderer.draw_brush, + (obj, context), "WINDOW", "POST_PIXEL") + + @staticmethod + def handle_remove(): + if MUV_UVSculptRenderer.__handle is not None: + sv = bpy.types.SpaceView3D + sv.draw_handler_remove( + MUV_UVSculptRenderer.__handle, "WINDOW") + MUV_UVSculptRenderer.__handle = None + + @staticmethod + def draw_brush(obj, context): + sc = context.scene + prefs = context.user_preferences.addons["uv_magic_uv"].preferences + + num_segment = 180 + theta = 2 * pi / num_segment + fact_t = tan(theta) + fact_r = cos(theta) + color = prefs.uvsculpt_brush_color + + bgl.glBegin(bgl.GL_LINE_STRIP) + bgl.glColor4f(color[0], color[1], color[2], color[3]) + x = sc.muv_uvsculpt_radius * cos(0.0) + y = sc.muv_uvsculpt_radius * sin(0.0) + for _ in range(num_segment): + bgl.glVertex2f(x + obj.current_mco.x, y + obj.current_mco.y) + tx = -y + ty = x + x = x + tx * fact_t + y = y + ty * fact_t + x = x * fact_r + y = y * fact_r + bgl.glEnd() + + +class MUV_UVSculptOps(bpy.types.Operator): + """ + Operation class: UV Sculpt in View3D + """ + + bl_idname = "uv.muv_uvsculpt_ops" + bl_label = "UV Sculpt" + bl_description = "UV Sculpt in View3D" + bl_options = {'REGISTER'} + + def __init__(self): + self.__timer = None + self.__loop_info = [] + self.__stroking = False + self.current_mco = Vector((0.0, 0.0)) + self.__initial_mco = Vector((0.0, 0.0)) + + def __get_strength(self, p, len_, factor): + f = factor + + if p > len_: + return 0.0 + + if p < 0.0: + return f + + return (len_ - p) * f / len_ + + def __stroke_init(self, context, _): + sc = context.scene + + self.__initial_mco = self.current_mco + + # get influenced UV + obj = context.active_object + world_mat = obj.matrix_world + bm = bmesh.from_edit_mesh(obj.data) + uv_layer = bm.loops.layers.uv.verify() + _, region, space = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D') + + self.__loop_info = [] + for f in bm.faces: + if not f.select: + continue + for i, l in enumerate(f.loops): + loc_2d = view3d_utils.location_3d_to_region_2d( + region, space.region_3d, world_mat * l.vert.co) + diff = loc_2d - self.__initial_mco + if diff.length < sc.muv_uvsculpt_radius: + info = { + "face_idx": f.index, + "loop_idx": i, + "initial_vco": l.vert.co.copy(), + "initial_vco_2d": loc_2d, + "initial_uv": l[uv_layer].uv.copy(), + "strength": self.__get_strength( + diff.length, sc.muv_uvsculpt_radius, + sc.muv_uvsculpt_strength) + } + self.__loop_info.append(info) + + def __stroke_apply(self, context, _): + sc = context.scene + obj = context.active_object + world_mat = obj.matrix_world + bm = bmesh.from_edit_mesh(obj.data) + uv_layer = bm.loops.layers.uv.verify() + mco = self.current_mco + + if sc.muv_uvsculpt_tools == 'GRAB': + for info in self.__loop_info: + diff_uv = (mco - self.__initial_mco) * info["strength"] + l = bm.faces[info["face_idx"]].loops[info["loop_idx"]] + l[uv_layer].uv = info["initial_uv"] + diff_uv / 100.0 + + elif sc.muv_uvsculpt_tools == 'PINCH': + _, region, space = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D') + loop_info = [] + for f in bm.faces: + if not f.select: + continue + for i, l in enumerate(f.loops): + loc_2d = view3d_utils.location_3d_to_region_2d( + region, space.region_3d, world_mat * l.vert.co) + diff = loc_2d - self.__initial_mco + if diff.length < sc.muv_uvsculpt_radius: + info = { + "face_idx": f.index, + "loop_idx": i, + "initial_vco": l.vert.co.copy(), + "initial_vco_2d": loc_2d, + "initial_uv": l[uv_layer].uv.copy(), + "strength": self.__get_strength( + diff.length, sc.muv_uvsculpt_radius, + sc.muv_uvsculpt_strength) + } + loop_info.append(info) + + # mouse coordinate to UV coordinate + ray_vec = view3d_utils.region_2d_to_vector_3d(region, + space.region_3d, mco) + ray_vec.normalize() + ray_orig = view3d_utils.region_2d_to_origin_3d(region, + space.region_3d, + mco) + ray_tgt = ray_orig + ray_vec * 1000000.0 + mwi = world_mat.inverted() + ray_orig_obj = mwi * ray_orig + ray_tgt_obj = mwi * ray_tgt + ray_dir_obj = ray_tgt_obj - ray_orig_obj + ray_dir_obj.normalize() + tree = BVHTree.FromBMesh(bm) + loc, _, fidx, _ = tree.ray_cast(ray_orig_obj, ray_dir_obj) + if not loc: + return + loops = [l for l in bm.faces[fidx].loops] + uvs = [Vector((l[uv_layer].uv.x, l[uv_layer].uv.y, 0.0)) + for l in loops] + target_uv = barycentric_transform( + loc, loops[0].vert.co, loops[1].vert.co, loops[2].vert.co, + uvs[0], uvs[1], uvs[2]) + target_uv = Vector((target_uv.x, target_uv.y)) + + # move to target UV coordinate + for info in loop_info: + l = bm.faces[info["face_idx"]].loops[info["loop_idx"]] + if sc.muv_uvsculpt_pinch_invert: + diff_uv = (l[uv_layer].uv - target_uv) * info["strength"] + else: + diff_uv = (target_uv - l[uv_layer].uv) * info["strength"] + l[uv_layer].uv = l[uv_layer].uv + diff_uv / 10.0 + + elif sc.muv_uvsculpt_tools == 'RELAX': + _, region, space = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D') + + # get vertex and loop relation + vert_db = {} + for f in bm.faces: + for l in f.loops: + if l.vert in vert_db: + vert_db[l.vert]["loops"].append(l) + else: + vert_db[l.vert] = {"loops": [l]} + + # get relaxation information + for k in vert_db.keys(): + d = vert_db[k] + d["uv_sum"] = Vector((0.0, 0.0)) + d["uv_count"] = 0 + + for l in d["loops"]: + ln = l.link_loop_next + lp = l.link_loop_prev + d["uv_sum"] = d["uv_sum"] + ln[uv_layer].uv + d["uv_sum"] = d["uv_sum"] + lp[uv_layer].uv + d["uv_count"] = d["uv_count"] + 2 + d["uv_p"] = d["uv_sum"] / d["uv_count"] + d["uv_b"] = d["uv_p"] - d["loops"][0][uv_layer].uv + for k in vert_db.keys(): + d = vert_db[k] + d["uv_sum_b"] = Vector((0.0, 0.0)) + for l in d["loops"]: + ln = l.link_loop_next + lp = l.link_loop_prev + dn = vert_db[ln.vert] + dp = vert_db[lp.vert] + d["uv_sum_b"] = d["uv_sum_b"] + dn["uv_b"] + dp["uv_b"] + + # apply + for f in bm.faces: + if not f.select: + continue + for i, l in enumerate(f.loops): + loc_2d = view3d_utils.location_3d_to_region_2d( + region, space.region_3d, world_mat * l.vert.co) + diff = loc_2d - self.__initial_mco + if diff.length >= sc.muv_uvsculpt_radius: + continue + db = vert_db[l.vert] + strength = self.__get_strength(diff.length, + sc.muv_uvsculpt_radius, + sc.muv_uvsculpt_strength) + + base = (1.0 - strength) * l[uv_layer].uv + if sc.muv_uvsculpt_relax_method == 'HC': + t = 0.5 * (db["uv_b"] + db["uv_sum_b"] / d["uv_count"]) + diff = strength * (db["uv_p"] - t) + target_uv = base + diff + elif sc.muv_uvsculpt_relax_method == 'LAPLACIAN': + diff = strength * db["uv_p"] + target_uv = base + diff + else: + continue + + l[uv_layer].uv = target_uv + + bmesh.update_edit_mesh(obj.data) + + def __stroke_exit(self, context, _): + sc = context.scene + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + uv_layer = bm.loops.layers.uv.verify() + mco = self.current_mco + + if sc.muv_uvsculpt_tools == 'GRAB': + for info in self.__loop_info: + diff_uv = (mco - self.__initial_mco) * info["strength"] + l = bm.faces[info["face_idx"]].loops[info["loop_idx"]] + l[uv_layer].uv = info["initial_uv"] + diff_uv / 100.0 + + bmesh.update_edit_mesh(obj.data) + + def modal(self, context, event): + props = context.scene.muv_props.uvsculpt + + if context.area: + context.area.tag_redraw() + + if not props.running: + if self.__timer is not None: + MUV_UVSculptRenderer.handle_remove() + context.window_manager.event_timer_remove(self.__timer) + self.__timer = None + return {'FINISHED'} + + self.current_mco = Vector((event.mouse_region_x, event.mouse_region_y)) + area, _, _ = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D') + + if self.current_mco.x < 0 or self.current_mco.x > area.width or \ + self.current_mco.y < 0 or self.current_mco.y > area.height: + return {'PASS_THROUGH'} + + if event.type == 'LEFTMOUSE': + if event.value == 'PRESS': + if not self.__stroking: + self.__stroke_init(context, event) + self.__stroking = True + elif event.value == 'RELEASE': + if self.__stroking: + self.__stroke_exit(context, event) + self.__stroking = False + elif event.type == 'MOUSEMOVE': + if self.__stroking: + self.__stroke_apply(context, event) + elif event.type == 'TIMER': + if self.__stroking: + self.__stroke_apply(context, event) + + return {'RUNNING_MODAL'} + + def invoke(self, context, _): + props = context.scene.muv_props.uvsculpt + + if context.area: + context.area.tag_redraw() + + if props.running: + props.running = False + return {'FINISHED'} + + props.running = True + if self.__timer is None: + self.__timer = context.window_manager.event_timer_add( + 0.1, context.window) + context.window_manager.modal_handler_add(self) + MUV_UVSculptRenderer.handle_add(self, context) + + return {'RUNNING_MODAL'} diff --git a/uv_magic_uv/muv_uvw_ops.py b/uv_magic_uv/op/uvw.py index eb366e97..10202677 100644 --- a/uv_magic_uv/muv_uvw_ops.py +++ b/uv_magic_uv/op/uvw.py @@ -20,9 +20,8 @@ __author__ = "Alexander Milovsky, Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" - +__version__ = "5.1" +__date__ = "24 Feb 2018" from math import sin, cos, pi @@ -30,11 +29,12 @@ import bpy import bmesh from bpy.props import ( FloatProperty, - FloatVectorProperty + FloatVectorProperty, + BoolProperty ) from mathutils import Vector -from . import muv_common +from .. import common class MUV_UVWBoxMap(bpy.types.Operator): @@ -62,6 +62,11 @@ class MUV_UVWBoxMap(bpy.types.Operator): default=1.0, precision=4 ) + assign_uvmap = BoolProperty( + name="Assign UVMap", + description="Assign UVMap when no UVmaps are available", + default=True + ) @classmethod def poll(cls, context): @@ -71,15 +76,17 @@ class MUV_UVWBoxMap(bpy.types.Operator): def execute(self, context): obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() # get UV layer if not bm.loops.layers.uv: - self.report( - {'WARNING'}, "Object must have more than one UV map") - return {'CANCELLED'} - + if self.assign_uvmap: + bm.loops.layers.uv.new() + else: + self.report( + {'WARNING'}, "Object must have more than one UV map") + return {'CANCELLED'} uv_layer = bm.loops.layers.uv.verify() scale = 1.0 / self.size @@ -168,6 +175,11 @@ class MUV_UVWBestPlanerMap(bpy.types.Operator): default=1.0, precision=4 ) + assign_uvmap = BoolProperty( + name="Assign UVMap", + description="Assign UVMap when no UVmaps are available", + default=True + ) @classmethod def poll(cls, context): @@ -177,14 +189,17 @@ class MUV_UVWBestPlanerMap(bpy.types.Operator): def execute(self, context): obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.faces.ensure_lookup_table() # get UV layer if not bm.loops.layers.uv: - self.report( - {'WARNING'}, "Object must have more than one UV map") - return {'CANCELLED'} + if self.assign_uvmap: + bm.loops.layers.uv.new() + else: + self.report( + {'WARNING'}, "Object must have more than one UV map") + return {'CANCELLED'} uv_layer = bm.loops.layers.uv.verify() diff --git a/uv_magic_uv/muv_wsuv_ops.py b/uv_magic_uv/op/world_scale_uv.py index 4ee8b4f9..e256fbac 100644 --- a/uv_magic_uv/muv_wsuv_ops.py +++ b/uv_magic_uv/op/world_scale_uv.py @@ -20,43 +20,32 @@ __author__ = "McBuff, Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "4.5" -__date__ = "19 Nov 2017" +__version__ = "5.1" +__date__ = "24 Feb 2018" +from math import sqrt import bpy import bmesh from mathutils import Vector -from bpy.props import ( - FloatProperty, - BoolProperty, - EnumProperty -) -from . import muv_common +from bpy.props import EnumProperty +from .. import common -def calc_edge_scale(uv_layer, loop0, loop1): - v0 = loop0.vert.co - v1 = loop1.vert.co - uv0 = loop0[uv_layer].uv.copy() - uv1 = loop1[uv_layer].uv.copy() - dv = v1 - v0 - duv = uv1 - uv0 +def measure_wsuv_info(obj): + mesh_area = common.measure_mesh_area(obj) + uv_area = common.measure_uv_area(obj) - scale = 0.0 - if dv.magnitude > 0.00000001: - scale = duv.magnitude / dv.magnitude + if not uv_area: + return None, None, None - return scale + if mesh_area == 0.0: + density = 0.0 + else: + density = sqrt(uv_area) / sqrt(mesh_area) - -def calc_face_scale(uv_layer, face): - es = 0.0 - for i, l in enumerate(face.loops[1:]): - es = es + calc_edge_scale(uv_layer, face.loops[i], l) - - return es + return uv_area, mesh_area, density class MUV_WSUVMeasure(bpy.types.Operator): @@ -70,30 +59,22 @@ class MUV_WSUVMeasure(bpy.types.Operator): bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - props = context.scene.muv_props.wsuv - obj = bpy.context.active_object - bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: - bm.verts.ensure_lookup_table() - bm.edges.ensure_lookup_table() - bm.faces.ensure_lookup_table() + sc = context.scene + obj = context.active_object - if not bm.loops.layers.uv: - self.report({'WARNING'}, "Object must have more than one UV map") + uv_area, mesh_area, density = measure_wsuv_info(obj) + if not uv_area: + self.report({'WARNING'}, + "Object must have more than one UV map and texture") return {'CANCELLED'} - uv_layer = bm.loops.layers.uv.verify() - sel_faces = [f for f in bm.faces if f.select] - - # measure average face size - scale = 0.0 - for f in sel_faces: - scale = scale + calc_face_scale(uv_layer, f) + sc.muv_wsuv_src_uv_area = uv_area + sc.muv_wsuv_src_mesh_area = mesh_area + sc.muv_wsuv_src_density = density - props.ref_scale = scale / len(sel_faces) - - self.report( - {'INFO'}, "Average face size: {0}".format(props.ref_scale)) + self.report({'INFO'}, + "UV Area: {0}, Mesh Area: {1}, Texel Density: {2}" + .format(uv_area, mesh_area, density)) return {'FINISHED'} @@ -108,16 +89,6 @@ class MUV_WSUVApply(bpy.types.Operator): bl_description = "Apply scaled UV based on scale calculation" bl_options = {'REGISTER', 'UNDO'} - proportional_scaling = BoolProperty( - name="Proportional Scaling", - default=True - ) - scaling_factor = FloatProperty( - name="Scaling Factor", - default=1.0, - max=1000.0, - min=0.00001 - ) origin = EnumProperty( name="Origin", description="Aspect Origin", @@ -139,43 +110,38 @@ class MUV_WSUVApply(bpy.types.Operator): def draw(self, _): layout = self.layout - row = layout.row() - row.prop(self, "proportional_scaling") - row = layout.row() - row.prop(self, "scaling_factor") - if self.proportional_scaling: - row.enabled = False + layout.prop(self, "origin") def execute(self, context): - props = context.scene.muv_props.wsuv - obj = bpy.context.active_object + sc = context.scene + obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) - if muv_common.check_version(2, 73, 0) >= 0: + if common.check_version(2, 73, 0) >= 0: bm.verts.ensure_lookup_table() bm.edges.ensure_lookup_table() bm.faces.ensure_lookup_table() - if not bm.loops.layers.uv: - self.report( - {'WARNING'}, "Object must have more than one UV map") - return {'CANCELLED'} - uv_layer = bm.loops.layers.uv.verify() - sel_faces = [f for f in bm.faces if f.select] - # measure average face size - scale = 0.0 - for f in sel_faces: - scale = scale + calc_face_scale(uv_layer, f) - scale = scale / len(sel_faces) + uv_area, mesh_area, density = measure_wsuv_info(obj) + if not uv_area: + self.report({'WARNING'}, + "Object must have more than one UV map and texture") + return {'CANCELLED'} - self.report( - {'INFO'}, "Average face size: {0}".format(scale)) + uv_layer = bm.loops.layers.uv.verify() - if self.proportional_scaling: - factor = props.ref_scale / scale - else: - factor = self.scaling_factor + if sc.muv_wsuv_mode == 'PROPORTIONAL': + tgt_density = sc.muv_wsuv_src_density * sqrt(mesh_area) / \ + sqrt(sc.muv_wsuv_src_mesh_area) + elif sc.muv_wsuv_mode == 'SCALING': + tgt_density = sc.muv_wsuv_src_density * sc.muv_wsuv_scaling_factor + elif sc.muv_wsuv_mode == 'USER': + tgt_density = sc.muv_wsuv_tgt_density + elif sc.muv_wsuv_mode == 'CONSTANT': + tgt_density = sc.muv_wsuv_src_density + + factor = tgt_density / density # calculate origin if self.origin == 'CENTER': diff --git a/uv_magic_uv/preferences.py b/uv_magic_uv/preferences.py new file mode 100644 index 00000000..d8cdf86b --- /dev/null +++ b/uv_magic_uv/preferences.py @@ -0,0 +1,216 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +from bpy.props import ( + FloatProperty, + FloatVectorProperty, +) +from bpy.types import AddonPreferences + + +class MUV_Preferences(AddonPreferences): + """Preferences class: Preferences for this add-on""" + + bl_idname = __package__ + + # for UV Sculpt + uvsculpt_brush_color = FloatVectorProperty( + name="Color", + description="Color", + default=(1.0, 0.4, 0.4, 1.0), + min=0.0, + max=1.0, + size=4, + subtype='COLOR' + ) + + # for Overlapped UV + uvinsp_overlapped_color = FloatVectorProperty( + name="Color", + description="Color", + default=(0.0, 0.0, 1.0, 0.3), + min=0.0, + max=1.0, + size=4, + subtype='COLOR' + ) + + # for Flipped UV + uvinsp_flipped_color = FloatVectorProperty( + name="Color", + description="Color", + default=(1.0, 0.0, 0.0, 0.3), + min=0.0, + max=1.0, + size=4, + subtype='COLOR' + ) + + # for Texture Projection + texproj_canvas_padding = FloatVectorProperty( + name="Canvas Padding", + description="Canvas Padding", + size=2, + max=50.0, + min=0.0, + default=(20.0, 20.0)) + + # for UV Bounding Box + uvbb_cp_size = FloatProperty( + name="Size", + description="Control Point Size", + default=6.0, + min=3.0, + max=100.0) + uvbb_cp_react_size = FloatProperty( + name="React Size", + description="Size event fired", + default=10.0, + min=3.0, + max=100.0) + + def draw(self, _): + layout = self.layout + + layout.label("[Configuration]") + + layout.label("UV Sculpt:") + sp = layout.split(percentage=0.05) + col = sp.column() # spacer + sp = sp.split(percentage=0.3) + col = sp.column() + col.label("Brush Color:") + col.prop(self, "uvsculpt_brush_color", text="") + + layout.separator() + + layout.label("UV Inspection:") + sp = layout.split(percentage=0.05) + col = sp.column() # spacer + sp = sp.split(percentage=0.3) + col = sp.column() + col.label("Overlapped UV Color:") + col.prop(self, "uvinsp_overlapped_color", text="") + sp = sp.split(percentage=0.45) + col = sp.column() + col.label("Flipped UV Color:") + col.prop(self, "uvinsp_flipped_color", text="") + + layout.separator() + + layout.label("Texture Projection:") + sp = layout.split(percentage=0.05) + col = sp.column() # spacer + sp = sp.split(percentage=0.3) + col = sp.column() + col.prop(self, "texproj_canvas_padding") + + layout.separator() + + layout.label("UV Bounding Box:") + sp = layout.split(percentage=0.05) + col = sp.column() # spacer + sp = sp.split(percentage=0.3) + col = sp.column() + col.label("Control Point:") + col.prop(self, "uvbb_cp_size") + col.prop(self, "uvbb_cp_react_size") + + layout.label("--------------------------------------") + + layout.label("[Description]") + column = layout.column(align=True) + column.label("Magic UV is composed of many UV editing features.") + column.label("See tutorial page if you are new to this add-on.") + column.label("https://github.com/nutti/Magic-UV/wiki/Tutorial") + + layout.label("--------------------------------------") + + layout.label("[Location]") + + row = layout.row(align=True) + sp = row.split(percentage=0.5) + sp.label("3D View > Tool shelf > Copy/Paste UV (Object mode)") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Copy/Paste UV (Among objects)") + + row = layout.row(align=True) + sp = row.split(percentage=0.5) + sp.label("3D View > Tool shelf > Copy/Paste UV (Edit mode)") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Copy/Paste UV (Among faces in 3D View)") + col.label("Transfer UV") + + row = layout.row(align=True) + sp = row.split(percentage=0.5) + sp.label("3D View > Tool shelf > UV Manipulation (Edit mode)") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Flip/Rotate UV") + col.label("Mirror UV") + col.label("Move UV") + col.label("World Scale UV") + col.label("Preserve UV Aspect") + col.label("Texture Lock") + col.label("Texture Wrap") + col.label("UV Sculpt") + + row = layout.row(align=True) + sp = row.split(percentage=0.5) + sp.label("3D View > Tool shelf > UV Manipulation (Edit mode)") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Unwrap Constraint") + col.label("Texture Projection") + col.label("UVW") + + row = layout.row(align=True) + sp = row.split(percentage=0.5) + sp.label("UV/Image Editor > Tool shelf > Copy/Paste UV") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Copy/Paste UV (Among faces in UV/Image Editor)") + + row = layout.row(align=True) + sp = row.split(percentage=0.5) + sp.label("UV/Image Editor > Tool shelf > UV Manipulation") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Align UV") + col.label("Smooth UV") + col.label("Select UV") + col.label("Pack UV (Extension)") + + row = layout.row(align=True) + sp = row.split(percentage=0.5) + sp.label("UV/Image Editor > Tool shelf > Editor Enhancement") + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("Align UV Cursor") + col.label("UV Cursor Location") + col.label("UV Bounding Box") + col.label("UV Inspection") diff --git a/uv_magic_uv/properites.py b/uv_magic_uv/properites.py new file mode 100644 index 00000000..3b61fcfc --- /dev/null +++ b/uv_magic_uv/properites.py @@ -0,0 +1,765 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import bpy +from bpy.props import ( + FloatProperty, + EnumProperty, + BoolProperty, + FloatVectorProperty, + IntProperty +) +from mathutils import Vector + +from . import common + + +def get_loaded_texture_name(_, __): + items = [(key, key, "") for key in bpy.data.images.keys()] + items.append(("None", "None", "")) + return items + + +# Properties used in this add-on. +class MUV_Properties(): + cpuv = None + cpuv_obj = None + cpuv_selseq = None + transuv = None + uvbb = None + texlock = None + texproj = None + texwrap = None + mvuv = None + uvinsp = None + uvsculpt = None + + def __init__(self): + self.cpuv = MUV_CPUVProps() + self.cpuv_obj = MUV_CPUVProps() + self.cpuv_selseq = MUV_CPUVSelSeqProps() + self.transuv = MUV_TransUVProps() + self.uvbb = MUV_UVBBProps() + self.texlock = MUV_TexLockProps() + self.texproj = MUV_TexProjProps() + self.texwrap = MUV_TexWrapProps() + self.mvuv = MUV_MVUVProps() + self.uvinsp = MUV_UVInspProps() + self.uvsculpt = MUV_UVSculptProps() + + +class MUV_CPUVProps(): + src_uvs = [] + src_pin_uvs = [] + src_seams = [] + + +class MUV_CPUVSelSeqProps(): + src_uvs = [] + src_pin_uvs = [] + src_seams = [] + + +class MUV_TransUVProps(): + topology_copied = [] + + +class MUV_TexProjProps(): + running = False + + +class MUV_UVBBProps(): + uv_info_ini = [] + ctrl_points_ini = [] + ctrl_points = [] + running = False + + +class MUV_TexLockProps(): + verts_orig = None + intr_verts_orig = None + intr_running = False + + +class MUV_TexWrapProps(): + ref_face_index = -1 + ref_obj = None + + +class MUV_MVUVProps(): + running = False + + +class MUV_UVInspProps(): + display_running = False + overlapped_info = [] + flipped_info = [] + + +class MUV_UVSculptProps(): + running = False + + +def init_props(scene): + scene.muv_props = MUV_Properties() + + # UV Sculpt + scene.muv_uvsculpt_enabled = BoolProperty( + name="UV Sculpt", + description="UV Sculpt is enabled", + default=False + ) + scene.muv_uvsculpt_radius = IntProperty( + name="Radius", + description="Radius of the brush", + min=1, + max=500, + default=30 + ) + scene.muv_uvsculpt_strength = FloatProperty( + name="Strength", + description="How powerful the effect of the brush when applied", + min=0.0, + max=1.0, + default=0.03, + ) + scene.muv_uvsculpt_tools = EnumProperty( + name="Tools", + description="Select Tools for the UV sculpt brushes", + items=[ + ('GRAB', "Grab", "Grab UVs"), + ('RELAX', "Relax", "Relax UVs"), + ('PINCH', "Pinch", "Pinch UVs") + ], + default='GRAB' + ) + scene.muv_uvsculpt_show_brush = BoolProperty( + name="Show Brush", + description="Show Brush", + default=True + ) + scene.muv_uvsculpt_pinch_invert = BoolProperty( + name="Invert", + description="Pinch UV to invert direction", + default=False + ) + scene.muv_uvsculpt_relax_method = EnumProperty( + name="Method", + description="Algorithm used for relaxation", + items=[ + ('HC', "HC", "Use HC method for relaxation"), + ('LAPLACIAN', "Laplacian", "Use laplacian method for relaxation") + ], + default='HC' + ) + + # Texture Wrap + scene.muv_texwrap_enabled = BoolProperty( + name="Texture Wrap", + description="Texture Wrap is enabled", + default=False + ) + scene.muv_texwrap_set_and_refer = BoolProperty( + name="Set and Refer", + description="Refer and set UV", + default=True + ) + scene.muv_texwrap_selseq = BoolProperty( + name="Selection Sequence", + description="Set UV sequentially", + default=False + ) + + # UV inspection + scene.muv_seluv_enabled = BoolProperty( + name="Select UV Enabled", + description="Select UV is enabled", + default=False + ) + scene.muv_uvinsp_enabled = BoolProperty( + name="UV Inspection Enabled", + description="UV Inspection is enabled", + default=False + ) + scene.muv_uvinsp_show_overlapped = BoolProperty( + name="Overlapped", + description="Show overlapped UVs", + default=False + ) + scene.muv_uvinsp_show_flipped = BoolProperty( + name="Flipped", + description="Show flipped UVs", + default=False + ) + scene.muv_uvinsp_show_mode = EnumProperty( + name="Mode", + description="Show mode", + items=[ + ('PART', "Part", "Show only overlapped/flipped part"), + ('FACE', "Face", "Show overlapped/flipped face") + ], + default='PART' + ) + + # Align UV + scene.muv_auv_enabled = BoolProperty( + name="Aline UV Enabled", + description="Align UV is enabled", + default=False + ) + scene.muv_auv_transmission = BoolProperty( + name="Transmission", + description="Align linked UVs", + default=False + ) + scene.muv_auv_select = BoolProperty( + name="Select", + description="Select UVs which are aligned", + default=False + ) + scene.muv_auv_vertical = BoolProperty( + name="Vert-Infl (Vertical)", + description="Align vertical direction influenced " + "by mesh vertex proportion", + default=False + ) + scene.muv_auv_horizontal = BoolProperty( + name="Vert-Infl (Horizontal)", + description="Align horizontal direction influenced " + "by mesh vertex proportion", + default=False + ) + scene.muv_auv_location = EnumProperty( + name="Location", + description="Align location", + items=[ + ('LEFT_TOP', "Left/Top", "Align to Left or Top"), + ('MIDDLE', "Middle", "Align to middle"), + ('RIGHT_BOTTOM', "Right/Bottom", "Align to Right or Bottom") + ], + default='MIDDLE' + ) + + # Smooth UV + scene.muv_smuv_enabled = BoolProperty( + name="Smooth UV Enabled", + description="Smooth UV is enabled", + default=False + ) + scene.muv_smuv_transmission = BoolProperty( + name="Transmission", + description="Smooth linked UVs", + default=False + ) + scene.muv_smuv_mesh_infl = FloatProperty( + name="Mesh Influence", + description="Influence rate of mesh vertex", + min=0.0, + max=1.0, + default=0.0 + ) + scene.muv_smuv_select = BoolProperty( + name="Select", + description="Select UVs which are smoothed", + default=False + ) + + # UV Bounding Box + scene.muv_uvbb_enabled = BoolProperty( + name="UV Bounding Box Enabled", + description="UV Bounding Box is enabled", + default=False + ) + scene.muv_uvbb_uniform_scaling = BoolProperty( + name="Uniform Scaling", + description="Enable Uniform Scaling", + default=False + ) + scene.muv_uvbb_boundary = EnumProperty( + name="Boundary", + description="Boundary", + default='UV_SEL', + items=[ + ('UV', "UV", "Boundary is decided by UV"), + ('UV_SEL', "UV (Selected)", "Boundary is decided by Selected UV") + ] + ) + + # Pack UV + scene.muv_packuv_enabled = BoolProperty( + name="Pack UV Enabled", + description="Pack UV is enabled", + default=False + ) + scene.muv_packuv_allowable_center_deviation = FloatVectorProperty( + name="Allowable Center Deviation", + description="Allowable center deviation to judge same UV island", + min=0.000001, + max=0.1, + default=(0.001, 0.001), + size=2 + ) + scene.muv_packuv_allowable_size_deviation = FloatVectorProperty( + name="Allowable Size Deviation", + description="Allowable sizse deviation to judge same UV island", + min=0.000001, + max=0.1, + default=(0.001, 0.001), + size=2 + ) + + # Move UV + scene.muv_mvuv_enabled = BoolProperty( + name="Move UV Enabled", + description="Move UV is enabled", + default=False + ) + + # UVW + scene.muv_uvw_enabled = BoolProperty( + name="UVW Enabled", + description="UVW is enabled", + default=False + ) + scene.muv_uvw_assign_uvmap = BoolProperty( + name="Assign UVMap", + description="Assign UVMap when no UVmaps are available", + default=True + ) + + # Texture Projection + scene.muv_texproj_enabled = BoolProperty( + name="Texture Projection Enabled", + description="Texture Projection is enabled", + default=False + ) + scene.muv_texproj_tex_magnitude = FloatProperty( + name="Magnitude", + description="Texture Magnitude", + default=0.5, + min=0.0, + max=100.0 + ) + scene.muv_texproj_tex_image = EnumProperty( + name="Image", + description="Texture Image", + items=get_loaded_texture_name + ) + scene.muv_texproj_tex_transparency = FloatProperty( + name="Transparency", + description="Texture Transparency", + default=0.2, + min=0.0, + max=1.0 + ) + scene.muv_texproj_adjust_window = BoolProperty( + name="Adjust Window", + description="Size of renderered texture is fitted to window", + default=True + ) + scene.muv_texproj_apply_tex_aspect = BoolProperty( + name="Texture Aspect Ratio", + description="Apply Texture Aspect ratio to displayed texture", + default=True + ) + scene.muv_texproj_assign_uvmap = BoolProperty( + name="Assign UVMap", + description="Assign UVMap when no UVmaps are available", + default=True + ) + + # Texture Lock + scene.muv_texlock_enabled = BoolProperty( + name="Texture Lock Enabled", + description="Texture Lock is enabled", + default=False + ) + scene.muv_texlock_connect = BoolProperty( + name="Connect UV", + default=True + ) + + # World Scale UV + scene.muv_wsuv_enabled = BoolProperty( + name="World Scale UV Enabled", + description="World Scale UV is enabled", + default=False + ) + scene.muv_wsuv_src_mesh_area = FloatProperty( + name="Mesh Area", + description="Source Mesh Area", + default=0.0, + min=0.0 + ) + scene.muv_wsuv_src_uv_area = FloatProperty( + name="UV Area", + description="Source UV Area", + default=0.0, + min=0.0 + ) + scene.muv_wsuv_src_density = FloatProperty( + name="Density", + description="Source Texel Density", + default=0.0, + min=0.0 + ) + scene.muv_wsuv_tgt_density = FloatProperty( + name="Density", + description="Target Texel Density", + default=0.0, + min=0.0 + ) + scene.muv_wsuv_mode = EnumProperty( + name="Mode", + description="Density calculation mode", + items=[ + ('PROPORTIONAL', 'Proportional', 'Scale proportionally by mesh'), + ('SCALING', 'Scaling', 'Specify scale factor'), + ('USER', 'User', 'Specify density'), + ('CONSTANT', 'Constant', 'Constant density') + ], + default='CONSTANT' + ) + scene.muv_wsuv_scaling_factor = FloatProperty( + name="Scaling Factor", + default=1.0, + max=1000.0, + min=0.00001 + ) + scene.muv_wsuv_origin = EnumProperty( + name="Origin", + description="Aspect Origin", + items=[ + ('CENTER', 'Center', 'Center'), + ('LEFT_TOP', 'Left Top', 'Left Bottom'), + ('LEFT_CENTER', 'Left Center', 'Left Center'), + ('LEFT_BOTTOM', 'Left Bottom', 'Left Bottom'), + ('CENTER_TOP', 'Center Top', 'Center Top'), + ('CENTER_BOTTOM', 'Center Bottom', 'Center Bottom'), + ('RIGHT_TOP', 'Right Top', 'Right Top'), + ('RIGHT_CENTER', 'Right Center', 'Right Center'), + ('RIGHT_BOTTOM', 'Right Bottom', 'Right Bottom') + + ], + default='CENTER' + ) + + # Unwrap Constraint + scene.muv_unwrapconst_enabled = BoolProperty( + name="Unwrap Constraint Enabled", + description="Unwrap Constraint is enabled", + default=False + ) + scene.muv_unwrapconst_u_const = BoolProperty( + name="U-Constraint", + description="Keep UV U-axis coordinate", + default=False + ) + scene.muv_unwrapconst_v_const = BoolProperty( + name="V-Constraint", + description="Keep UV V-axis coordinate", + default=False + ) + + # Preserve UV Aspect + scene.muv_preserve_uv_enabled = BoolProperty( + name="Preserve UV Aspect Enabled", + description="Preserve UV Aspect is enabled", + default=False + ) + scene.muv_preserve_uv_tex_image = EnumProperty( + name="Image", + description="Texture Image", + items=get_loaded_texture_name + ) + scene.muv_preserve_uv_origin = EnumProperty( + name="Origin", + description="Aspect Origin", + items=[ + ('CENTER', 'Center', 'Center'), + ('LEFT_TOP', 'Left Top', 'Left Bottom'), + ('LEFT_CENTER', 'Left Center', 'Left Center'), + ('LEFT_BOTTOM', 'Left Bottom', 'Left Bottom'), + ('CENTER_TOP', 'Center Top', 'Center Top'), + ('CENTER_BOTTOM', 'Center Bottom', 'Center Bottom'), + ('RIGHT_TOP', 'Right Top', 'Right Top'), + ('RIGHT_CENTER', 'Right Center', 'Right Center'), + ('RIGHT_BOTTOM', 'Right Bottom', 'Right Bottom') + + ], + default="CENTER" + ) + + # Flip/Rotate UV + scene.muv_fliprot_enabled = BoolProperty( + name="Flip/Rotate UV Enabled", + description="Flip/Rotate UV is enabled", + default=False + ) + scene.muv_fliprot_seams = BoolProperty( + name="Seams", + description="Seams", + default=True + ) + + # Mirror UV + scene.muv_mirroruv_enabled = BoolProperty( + name="Mirror UV Enabled", + description="Mirror UV is enabled", + default=False + ) + scene.muv_mirroruv_axis = EnumProperty( + items=[ + ('X', "X", "Mirror Along X axis"), + ('Y', "Y", "Mirror Along Y axis"), + ('Z', "Z", "Mirror Along Z axis") + ], + name="Axis", + description="Mirror Axis", + default='X' + ) + + # Copy/Paste UV + scene.muv_cpuv_enabled = BoolProperty( + name="Copy/Paste UV Enabled", + description="Copy/Paste UV is enabled", + default=False + ) + scene.muv_cpuv_copy_seams = BoolProperty( + name="Copy Seams", + description="Copy Seams", + default=True + ) + scene.muv_cpuv_mode = EnumProperty( + items=[ + ('DEFAULT', "Default", "Default Mode"), + ('SEL_SEQ', "Selection Sequence", "Selection Sequence Mode") + ], + name="Copy/Paste UV Mode", + description="Copy/Paste UV Mode", + default='DEFAULT' + ) + scene.muv_cpuv_strategy = EnumProperty( + name="Strategy", + description="Paste Strategy", + items=[ + ('N_N', 'N:N', 'Number of faces must be equal to source'), + ('N_M', 'N:M', 'Number of faces must not be equal to source') + ], + default='N_M' + ) + + # Transfer UV + scene.muv_transuv_enabled = BoolProperty( + name="Transfer UV Enabled", + description="Transfer UV is enabled", + default=False + ) + scene.muv_transuv_invert_normals = BoolProperty( + name="Invert Normals", + description="Invert Normals", + default=False + ) + scene.muv_transuv_copy_seams = BoolProperty( + name="Copy Seams", + description="Copy Seams", + default=True + ) + + # Align UV Cursor + def auvc_get_cursor_loc(self): + area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW', + 'IMAGE_EDITOR') + bd_size = common.get_uvimg_editor_board_size(area) + loc = space.cursor_location + if bd_size[0] < 0.000001: + cx = 0.0 + else: + cx = loc[0] / bd_size[0] + if bd_size[1] < 0.000001: + cy = 0.0 + else: + cy = loc[1] / bd_size[1] + self['muv_auvc_cursor_loc'] = Vector((cx, cy)) + return self.get('muv_auvc_cursor_loc', (0.0, 0.0)) + + def auvc_set_cursor_loc(self, value): + self['muv_auvc_cursor_loc'] = value + area, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW', + 'IMAGE_EDITOR') + bd_size = common.get_uvimg_editor_board_size(area) + cx = bd_size[0] * value[0] + cy = bd_size[1] * value[1] + space.cursor_location = Vector((cx, cy)) + + scene.muv_auvc_enabled = BoolProperty( + name="Align UV Cursor Enabled", + description="Align UV Cursor is enabled", + default=False + ) + scene.muv_auvc_cursor_loc = FloatVectorProperty( + name="UV Cursor Location", + size=2, + precision=4, + soft_min=-1.0, + soft_max=1.0, + step=1, + default=(0.000, 0.000), + get=auvc_get_cursor_loc, + set=auvc_set_cursor_loc + ) + scene.muv_auvc_align_menu = EnumProperty( + name="Align Method", + description="Align Method", + default='TEXTURE', + items=[ + ('TEXTURE', "Texture", "Align to texture"), + ('UV', "UV", "Align to UV"), + ('UV_SEL', "UV (Selected)", "Align to Selected UV") + ] + ) + + # UV Cursor Location + scene.muv_uvcloc_enabled = BoolProperty( + name="UV Cursor Location Enabled", + description="UV Cursor Location is enabled", + default=False + ) + + +def clear_props(scene): + del scene.muv_props + + # UV Sculpt + del scene.muv_uvsculpt_enabled + del scene.muv_uvsculpt_radius + del scene.muv_uvsculpt_strength + del scene.muv_uvsculpt_tools + del scene.muv_uvsculpt_show_brush + del scene.muv_uvsculpt_pinch_invert + del scene.muv_uvsculpt_relax_method + + # Texture Wrap + del scene.muv_texwrap_enabled + del scene.muv_texwrap_set_and_refer + del scene.muv_texwrap_selseq + + # UV Inspection + del scene.muv_seluv_enabled + del scene.muv_uvinsp_enabled + del scene.muv_uvinsp_show_overlapped + del scene.muv_uvinsp_show_flipped + del scene.muv_uvinsp_show_mode + + # Align UV + del scene.muv_auv_enabled + del scene.muv_auv_transmission + del scene.muv_auv_select + del scene.muv_auv_vertical + del scene.muv_auv_horizontal + del scene.muv_auv_location + + # Smooth UV + del scene.muv_smuv_enabled + del scene.muv_smuv_transmission + del scene.muv_smuv_mesh_infl + del scene.muv_smuv_select + + # UV Bounding Box + del scene.muv_uvbb_enabled + del scene.muv_uvbb_uniform_scaling + del scene.muv_uvbb_boundary + + # Pack UV + del scene.muv_packuv_enabled + del scene.muv_packuv_allowable_center_deviation + del scene.muv_packuv_allowable_size_deviation + + # Move UV + del scene.muv_mvuv_enabled + + # UVW + del scene.muv_uvw_enabled + del scene.muv_uvw_assign_uvmap + + # Texture Projection + del scene.muv_texproj_enabled + del scene.muv_texproj_tex_magnitude + del scene.muv_texproj_tex_image + del scene.muv_texproj_tex_transparency + del scene.muv_texproj_adjust_window + del scene.muv_texproj_apply_tex_aspect + del scene.muv_texproj_assign_uvmap + + # Texture Lock + del scene.muv_texlock_enabled + del scene.muv_texlock_connect + + # World Scale UV + del scene.muv_wsuv_enabled + del scene.muv_wsuv_src_mesh_area + del scene.muv_wsuv_src_uv_area + del scene.muv_wsuv_src_density + del scene.muv_wsuv_tgt_density + del scene.muv_wsuv_mode + del scene.muv_wsuv_scaling_factor + del scene.muv_wsuv_origin + + # Unwrap Constraint + del scene.muv_unwrapconst_enabled + del scene.muv_unwrapconst_u_const + del scene.muv_unwrapconst_v_const + + # Preserve UV Aspect + del scene.muv_preserve_uv_enabled + del scene.muv_preserve_uv_tex_image + del scene.muv_preserve_uv_origin + + # Flip/Rotate UV + del scene.muv_fliprot_enabled + del scene.muv_fliprot_seams + + # Mirror UV + del scene.muv_mirroruv_enabled + del scene.muv_mirroruv_axis + + # Copy/Paste UV + del scene.muv_cpuv_enabled + del scene.muv_cpuv_copy_seams + del scene.muv_cpuv_mode + del scene.muv_cpuv_strategy + + # Transfer UV + del scene.muv_transuv_enabled + del scene.muv_transuv_invert_normals + del scene.muv_transuv_copy_seams + + # Align UV Cursor + del scene.muv_auvc_enabled + del scene.muv_auvc_cursor_loc + del scene.muv_auvc_align_menu + + # UV Cursor Location + del scene.muv_uvcloc_enabled diff --git a/uv_magic_uv/ui/__init__.py b/uv_magic_uv/ui/__init__.py new file mode 100644 index 00000000..ad56aeb3 --- /dev/null +++ b/uv_magic_uv/ui/__init__.py @@ -0,0 +1,44 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +if "bpy" in locals(): + import importlib + importlib.reload(view3d_copy_paste_uv_objectmode) + importlib.reload(view3d_copy_paste_uv_editmode) + importlib.reload(view3d_uv_manipulation) + importlib.reload(view3d_uv_mapping) + importlib.reload(uvedit_copy_paste_uv) + importlib.reload(uvedit_uv_manipulation) + importlib.reload(uvedit_editor_enhance) +else: + from . import view3d_copy_paste_uv_objectmode + from . import view3d_copy_paste_uv_editmode + from . import view3d_uv_manipulation + from . import view3d_uv_mapping + from . import uvedit_copy_paste_uv + from . import uvedit_uv_manipulation + from . import uvedit_editor_enhance + +import bpy diff --git a/uv_magic_uv/ui/uvedit_copy_paste_uv.py b/uv_magic_uv/ui/uvedit_copy_paste_uv.py new file mode 100644 index 00000000..d87dbef3 --- /dev/null +++ b/uv_magic_uv/ui/uvedit_copy_paste_uv.py @@ -0,0 +1,54 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import bpy + +from ..op import copy_paste_uv_uvedit + + +class IMAGE_PT_MUV_CPUV(bpy.types.Panel): + """ + Panel class: Copy/Paste UV on Property Panel on UV/ImageEditor + """ + + bl_space_type = 'IMAGE_EDITOR' + bl_region_type = 'TOOLS' + bl_label = "Copy/Paste UV" + bl_category = "Magic UV" + bl_context = 'mesh_edit' + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + layout.label(text="", icon='IMAGE_COL') + + def draw(self, _): + layout = self.layout + + row = layout.row(align=True) + row.operator(copy_paste_uv_uvedit.MUV_CPUVIECopyUV.bl_idname, + text="Copy") + row.operator(copy_paste_uv_uvedit.MUV_CPUVIEPasteUV.bl_idname, + text="Paste") diff --git a/uv_magic_uv/ui/uvedit_editor_enhance.py b/uv_magic_uv/ui/uvedit_editor_enhance.py new file mode 100644 index 00000000..88a2492c --- /dev/null +++ b/uv_magic_uv/ui/uvedit_editor_enhance.py @@ -0,0 +1,136 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import bpy + +from ..op import align_uv_cursor +from ..op import uv_bounding_box +from ..op import uv_inspection + + +class IMAGE_PT_MUV_EE(bpy.types.Panel): + """ + Panel class: UV/Image Editor Enhancement + """ + + bl_space_type = 'IMAGE_EDITOR' + bl_region_type = 'TOOLS' + bl_label = "Editor Enhancement" + bl_category = "Magic UV" + bl_context = 'mesh_edit' + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + layout.label(text="", icon='IMAGE_COL') + + def draw(self, context): + layout = self.layout + sc = context.scene + props = sc.muv_props + + box = layout.box() + box.prop(sc, "muv_auvc_enabled", text="Align UV Cursor") + if sc.muv_auvc_enabled: + box.prop(sc, "muv_auvc_align_menu", expand=True) + + col = box.column(align=True) + + row = col.row(align=True) + ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname, + text="Left Top") + ops.position = 'LEFT_TOP' + ops.base = sc.muv_auvc_align_menu + ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname, + text="Middle Top") + ops.position = 'MIDDLE_TOP' + ops.base = sc.muv_auvc_align_menu + ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname, + text="Right Top") + ops.position = 'RIGHT_TOP' + ops.base = sc.muv_auvc_align_menu + + row = col.row(align=True) + ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname, + text="Left Middle") + ops.position = 'LEFT_MIDDLE' + ops.base = sc.muv_auvc_align_menu + ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname, + text="Center") + ops.position = 'CENTER' + ops.base = sc.muv_auvc_align_menu + ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname, + text="Right Middle") + ops.position = 'RIGHT_MIDDLE' + ops.base = sc.muv_auvc_align_menu + + row = col.row(align=True) + ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname, + text="Left Bottom") + ops.position = 'LEFT_BOTTOM' + ops.base = sc.muv_auvc_align_menu + ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname, + text="Middle Bottom") + ops.position = 'MIDDLE_BOTTOM' + ops.base = sc.muv_auvc_align_menu + ops = row.operator(align_uv_cursor.MUV_AUVCAlignOps.bl_idname, + text="Right Bottom") + ops.position = 'RIGHT_BOTTOM' + ops.base = sc.muv_auvc_align_menu + + box = layout.box() + box.prop(sc, "muv_uvcloc_enabled", text="UV Cursor Location") + if sc.muv_uvcloc_enabled: + box.prop(sc, "muv_auvc_cursor_loc", text="") + + box = layout.box() + box.prop(sc, "muv_uvbb_enabled", text="UV Bounding Box") + if sc.muv_uvbb_enabled: + if props.uvbb.running is False: + box.operator(uv_bounding_box.MUV_UVBBUpdater.bl_idname, + text="Display", icon='PLAY') + else: + box.operator(uv_bounding_box.MUV_UVBBUpdater.bl_idname, + text="Hide", icon='PAUSE') + box.prop(sc, "muv_uvbb_uniform_scaling", text="Uniform Scaling") + box.prop(sc, "muv_uvbb_boundary", text="Boundary") + + box = layout.box() + box.prop(sc, "muv_uvinsp_enabled", text="UV Inspection") + if sc.muv_uvinsp_enabled: + row = box.row() + if not sc.muv_props.uvinsp.display_running: + row.operator(uv_inspection.MUV_UVInspDisplay.bl_idname, + text="Display", icon='PLAY') + else: + row.operator(uv_inspection.MUV_UVInspDisplay.bl_idname, + text="Hide", icon='PAUSE') + row.operator(uv_inspection.MUV_UVInspUpdate.bl_idname, + text="Update") + row = box.row() + row.prop(sc, "muv_uvinsp_show_overlapped") + row.prop(sc, "muv_uvinsp_show_flipped") + row = box.row() + row.prop(sc, "muv_uvinsp_show_mode") diff --git a/uv_magic_uv/ui/uvedit_uv_manipulation.py b/uv_magic_uv/ui/uvedit_uv_manipulation.py new file mode 100644 index 00000000..f391c4cb --- /dev/null +++ b/uv_magic_uv/ui/uvedit_uv_manipulation.py @@ -0,0 +1,117 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import bpy + +from ..op import uv_inspection +from ..op import align_uv +from ..op import smooth_uv +from ..op import pack_uv + + +class IMAGE_PT_MUV_UVManip(bpy.types.Panel): + """ + Panel class: UV Manipulation on Property Panel on UV/ImageEditor + """ + + bl_space_type = 'IMAGE_EDITOR' + bl_region_type = 'TOOLS' + bl_label = "UV Manipulation" + bl_category = "Magic UV" + bl_context = 'mesh_edit' + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + layout.label(text="", icon='IMAGE_COL') + + def draw(self, context): + sc = context.scene + layout = self.layout + + box = layout.box() + box.prop(sc, "muv_auv_enabled", text="Align UV") + if sc.muv_auv_enabled: + col = box.column() + row = col.row(align=True) + ops = row.operator(align_uv.MUV_AUVCircle.bl_idname, text="Circle") + ops.transmission = sc.muv_auv_transmission + ops.select = sc.muv_auv_select + ops = row.operator(align_uv.MUV_AUVStraighten.bl_idname, + text="Straighten") + ops.transmission = sc.muv_auv_transmission + ops.select = sc.muv_auv_select + ops.vertical = sc.muv_auv_vertical + ops.horizontal = sc.muv_auv_horizontal + row = col.row() + ops = row.operator(align_uv.MUV_AUVAxis.bl_idname, text="XY-axis") + ops.transmission = sc.muv_auv_transmission + ops.select = sc.muv_auv_select + ops.vertical = sc.muv_auv_vertical + ops.horizontal = sc.muv_auv_horizontal + ops.location = sc.muv_auv_location + row.prop(sc, "muv_auv_location", text="") + + col = box.column(align=True) + row = col.row(align=True) + row.prop(sc, "muv_auv_transmission", text="Transmission") + row.prop(sc, "muv_auv_select", text="Select") + row = col.row(align=True) + row.prop(sc, "muv_auv_vertical", text="Vertical") + row.prop(sc, "muv_auv_horizontal", text="Horizontal") + + box = layout.box() + box.prop(sc, "muv_smuv_enabled", text="Smooth UV") + if sc.muv_smuv_enabled: + ops = box.operator(smooth_uv.MUV_AUVSmooth.bl_idname, + text="Smooth") + ops.transmission = sc.muv_smuv_transmission + ops.select = sc.muv_smuv_select + ops.mesh_infl = sc.muv_smuv_mesh_infl + col = box.column(align=True) + row = col.row(align=True) + row.prop(sc, "muv_smuv_transmission", text="Transmission") + row.prop(sc, "muv_smuv_select", text="Select") + col.prop(sc, "muv_smuv_mesh_infl", text="Mesh Influence") + + box = layout.box() + box.prop(sc, "muv_seluv_enabled", text="Select UV") + if sc.muv_seluv_enabled: + row = box.row(align=True) + row.operator(uv_inspection.MUV_UVInspSelectOverlapped.bl_idname) + row.operator(uv_inspection.MUV_UVInspSelectFlipped.bl_idname) + + box = layout.box() + box.prop(sc, "muv_packuv_enabled", text="Pack UV (Extension)") + if sc.muv_packuv_enabled: + ops = box.operator(pack_uv.MUV_PackUV.bl_idname, text="Pack UV") + ops.allowable_center_deviation = \ + sc.muv_packuv_allowable_center_deviation + ops.allowable_size_deviation = \ + sc.muv_packuv_allowable_size_deviation + box.label("Allowable Center Deviation:") + box.prop(sc, "muv_packuv_allowable_center_deviation", text="") + box.label("Allowable Size Deviation:") + box.prop(sc, "muv_packuv_allowable_size_deviation", text="") diff --git a/uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py b/uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py new file mode 100644 index 00000000..a22adf03 --- /dev/null +++ b/uv_magic_uv/ui/view3d_copy_paste_uv_editmode.py @@ -0,0 +1,81 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import bpy + +from ..op import copy_paste_uv +from ..op import transfer_uv + + +class OBJECT_PT_MUV_CPUV(bpy.types.Panel): + """ + Panel class: Copy/Paste UV on Property Panel on View3D + """ + + bl_space_type = 'VIEW_3D' + bl_region_type = 'TOOLS' + bl_label = "Copy/Paste UV" + bl_category = "Magic UV" + bl_context = 'mesh_edit' + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + layout.label(text="", icon='IMAGE_COL') + + def draw(self, context): + sc = context.scene + layout = self.layout + + box = layout.box() + box.prop(sc, "muv_cpuv_enabled", text="Copy/Paste UV") + if sc.muv_cpuv_enabled: + row = box.row(align=True) + if sc.muv_cpuv_mode == 'DEFAULT': + row.menu(copy_paste_uv.MUV_CPUVCopyUVMenu.bl_idname, + text="Copy") + row.menu(copy_paste_uv.MUV_CPUVPasteUVMenu.bl_idname, + text="Paste") + elif sc.muv_cpuv_mode == 'SEL_SEQ': + row.menu(copy_paste_uv.MUV_CPUVSelSeqCopyUVMenu.bl_idname, + text="Copy") + row.menu(copy_paste_uv.MUV_CPUVSelSeqPasteUVMenu.bl_idname, + text="Paste") + box.prop(sc, "muv_cpuv_mode", expand=True) + box.prop(sc, "muv_cpuv_copy_seams", text="Seams") + box.prop(sc, "muv_cpuv_strategy", text="Strategy") + + box = layout.box() + box.prop(sc, "muv_transuv_enabled", text="Transfer UV") + if sc.muv_transuv_enabled: + row = box.row(align=True) + row.operator(transfer_uv.MUV_TransUVCopy.bl_idname, text="Copy") + ops = row.operator(transfer_uv.MUV_TransUVPaste.bl_idname, + text="Paste") + ops.invert_normals = sc.muv_transuv_invert_normals + ops.copy_seams = sc.muv_transuv_copy_seams + row = box.row() + row.prop(sc, "muv_transuv_invert_normals", text="Invert Normals") + row.prop(sc, "muv_transuv_copy_seams", text="Seams") diff --git a/uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py b/uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py new file mode 100644 index 00000000..f9e2bec0 --- /dev/null +++ b/uv_magic_uv/ui/view3d_copy_paste_uv_objectmode.py @@ -0,0 +1,56 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import bpy + +from ..op import copy_paste_uv_object + + +class OBJECT_PT_MUV_CPUVObj(bpy.types.Panel): + """ + Panel class: Copy/Paste UV on Property Panel on View3D + """ + + bl_space_type = 'VIEW_3D' + bl_region_type = 'TOOLS' + bl_label = "Copy/Paste UV" + bl_category = "Magic UV" + bl_context = 'objectmode' + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + layout.label(text="", icon='IMAGE_COL') + + def draw(self, context): + sc = context.scene + layout = self.layout + + row = layout.row(align=True) + row.menu(copy_paste_uv_object.MUV_CPUVObjCopyUVMenu.bl_idname, + text="Copy") + row.menu(copy_paste_uv_object.MUV_CPUVObjPasteUVMenu.bl_idname, + text="Paste") + layout.prop(sc, "muv_cpuv_copy_seams", text="Copy Seams") diff --git a/uv_magic_uv/ui/view3d_uv_manipulation.py b/uv_magic_uv/ui/view3d_uv_manipulation.py new file mode 100644 index 00000000..1e9b7d7e --- /dev/null +++ b/uv_magic_uv/ui/view3d_uv_manipulation.py @@ -0,0 +1,180 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import bpy + +from ..op import flip_rotate_uv +from ..op import mirror_uv +from ..op import move_uv +from ..op import preserve_uv_aspect +from ..op import texture_lock +from ..op import texture_wrap +from ..op import uv_sculpt +from ..op import world_scale_uv + + +class OBJECT_PT_MUV_UVManip(bpy.types.Panel): + """ + Panel class: UV Manipulation on Property Panel on View3D + """ + + bl_space_type = 'VIEW_3D' + bl_region_type = 'TOOLS' + bl_label = "UV Manipulation" + bl_category = "Magic UV" + bl_context = 'mesh_edit' + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + layout.label(text="", icon='IMAGE_COL') + + def draw(self, context): + sc = context.scene + props = sc.muv_props + layout = self.layout + + box = layout.box() + box.prop(sc, "muv_fliprot_enabled", text="Flip/Rotate UV") + if sc.muv_fliprot_enabled: + row = box.row() + ops = row.operator(flip_rotate_uv.MUV_FlipRot.bl_idname, + text="Flip/Rotate") + ops.seams = sc.muv_fliprot_seams + row.prop(sc, "muv_fliprot_seams", text="Seams") + + box = layout.box() + box.prop(sc, "muv_mirroruv_enabled", text="Mirror UV") + if sc.muv_mirroruv_enabled: + row = box.row() + ops = row.operator(mirror_uv.MUV_MirrorUV.bl_idname, text="Mirror") + ops.axis = sc.muv_mirroruv_axis + row.prop(sc, "muv_mirroruv_axis", text="") + + box = layout.box() + box.prop(sc, "muv_mvuv_enabled", text="Move UV") + if sc.muv_mvuv_enabled: + col = box.column() + col.operator(move_uv.MUV_MVUV.bl_idname, icon='PLAY', text="Start") + if props.mvuv.running: + col.enabled = False + else: + col.enabled = True + + box = layout.box() + box.prop(sc, "muv_wsuv_enabled", text="World Scale UV") + if sc.muv_wsuv_enabled: + row = box.row(align=True) + row.operator(world_scale_uv.MUV_WSUVMeasure.bl_idname, + text="Measure") + ops = row.operator(world_scale_uv.MUV_WSUVApply.bl_idname, + text="Apply") + ops.origin = sc.muv_wsuv_origin + box.label("Source:") + sp = box.split(percentage=0.7) + col = sp.column(align=True) + col.prop(sc, "muv_wsuv_src_mesh_area", text="Mesh Area") + col.prop(sc, "muv_wsuv_src_uv_area", text="UV Area") + col.prop(sc, "muv_wsuv_src_density", text="Density") + col.enabled = False + sp = sp.split(percentage=1.0) + col = sp.column(align=True) + col.label("cm x cm") + col.label("px x px") + col.label("px/cm") + col.enabled = False + sp = box.split(percentage=0.3) + sp.label("Mode:") + sp = sp.split(percentage=1.0) + col = sp.column() + col.prop(sc, "muv_wsuv_mode", text="") + if sc.muv_wsuv_mode == 'USER': + col.prop(sc, "muv_wsuv_tgt_density", text="Density") + if sc.muv_wsuv_mode == 'SCALING': + col.prop(sc, "muv_wsuv_scaling_factor", text="Scaling Factor") + box.prop(sc, "muv_wsuv_origin", text="Origin") + + box = layout.box() + box.prop(sc, "muv_preserve_uv_enabled", text="Preserve UV Aspect") + if sc.muv_preserve_uv_enabled: + row = box.row() + ops = row.operator( + preserve_uv_aspect.MUV_PreserveUVAspect.bl_idname, + text="Change Image") + ops.dest_img_name = sc.muv_preserve_uv_tex_image + ops.origin = sc.muv_preserve_uv_origin + row.prop(sc, "muv_preserve_uv_tex_image", text="") + box.prop(sc, "muv_preserve_uv_origin", text="Origin") + + box = layout.box() + box.prop(sc, "muv_texlock_enabled", text="Texture Lock") + if sc.muv_texlock_enabled: + row = box.row(align=True) + col = row.column(align=True) + col.label("Normal Mode:") + col = row.column(align=True) + col.operator(texture_lock.MUV_TexLockStart.bl_idname, text="Lock") + ops = col.operator(texture_lock.MUV_TexLockStop.bl_idname, + text="Unlock") + ops.connect = sc.muv_texlock_connect + col.prop(sc, "muv_texlock_connect", text="Connect") + + row = box.row(align=True) + row.label("Interactive Mode:") + if not props.texlock.intr_running: + row.operator(texture_lock.MUV_TexLockIntrStart.bl_idname, + icon='PLAY', text="Start") + else: + row.operator(texture_lock.MUV_TexLockIntrStop.bl_idname, + icon="PAUSE", text="Stop") + + box = layout.box() + box.prop(sc, "muv_texwrap_enabled", text="Texture Wrap") + if sc.muv_texwrap_enabled: + row = box.row(align=True) + row.operator(texture_wrap.MUV_TexWrapRefer.bl_idname, text="Refer") + row.operator(texture_wrap.MUV_TexWrapSet.bl_idname, text="Set") + box.prop(sc, "muv_texwrap_set_and_refer") + box.prop(sc, "muv_texwrap_selseq") + + box = layout.box() + box.prop(sc, "muv_uvsculpt_enabled", text="UV Sculpt") + if sc.muv_uvsculpt_enabled: + if not props.uvsculpt.running: + box.operator(uv_sculpt.MUV_UVSculptOps.bl_idname, + icon='PLAY', text="Start") + else: + box.operator(uv_sculpt.MUV_UVSculptOps.bl_idname, + icon='PAUSE', text="Stop") + col = box.column() + col.label("Brush:") + col.prop(sc, "muv_uvsculpt_radius") + col.prop(sc, "muv_uvsculpt_strength") + box.prop(sc, "muv_uvsculpt_tools") + if sc.muv_uvsculpt_tools == 'PINCH': + box.prop(sc, "muv_uvsculpt_pinch_invert") + elif sc.muv_uvsculpt_tools == 'RELAX': + box.prop(sc, "muv_uvsculpt_relax_method") + box.prop(sc, "muv_uvsculpt_show_brush") diff --git a/uv_magic_uv/ui/view3d_uv_mapping.py b/uv_magic_uv/ui/view3d_uv_mapping.py new file mode 100644 index 00000000..2dc241c0 --- /dev/null +++ b/uv_magic_uv/ui/view3d_uv_mapping.py @@ -0,0 +1,99 @@ +# <pep8-80 compliant> + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +__author__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.1" +__date__ = "24 Feb 2018" + +import bpy + +from ..op import texture_projection +from ..op import unwrap_constraint +from ..op import uvw + + +class OBJECT_PT_MUV_UVMapping(bpy.types.Panel): + """ + Panel class: UV Mapping on Property Panel on View3D + """ + + bl_space_type = 'VIEW_3D' + bl_region_type = 'TOOLS' + bl_label = "UV Mapping" + bl_category = "Magic UV" + bl_context = 'mesh_edit' + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _): + layout = self.layout + layout.label(text="", icon='IMAGE_COL') + + def draw(self, context): + sc = context.scene + props = sc.muv_props + layout = self.layout + + box = layout.box() + box.prop(sc, "muv_unwrapconst_enabled", text="Unwrap Constraint") + if sc.muv_unwrapconst_enabled: + ops = box.operator( + unwrap_constraint.MUV_UnwrapConstraint.bl_idname, + text="Unwrap") + ops.u_const = sc.muv_unwrapconst_u_const + ops.v_const = sc.muv_unwrapconst_v_const + row = box.row(align=True) + row.prop(sc, "muv_unwrapconst_u_const", text="U-Constraint") + row.prop(sc, "muv_unwrapconst_v_const", text="V-Constraint") + + box = layout.box() + box.prop(sc, "muv_texproj_enabled", text="Texture Projection") + if sc.muv_texproj_enabled: + row = box.row() + if not props.texproj.running: + row.operator(texture_projection.MUV_TexProjStart.bl_idname, + text="Start", icon='PLAY') + else: + row.operator(texture_projection.MUV_TexProjStop.bl_idname, + text="Stop", icon='PAUSE') + row.prop(sc, "muv_texproj_tex_image", text="") + box.prop(sc, "muv_texproj_tex_transparency", text="Transparency") + col = box.column(align=True) + row = col.row() + row.prop(sc, "muv_texproj_adjust_window", text="Adjust Window") + if not sc.muv_texproj_adjust_window: + row.prop(sc, "muv_texproj_tex_magnitude", text="Magnitude") + col.prop(sc, "muv_texproj_apply_tex_aspect", + text="Texture Aspect Ratio") + col.prop(sc, "muv_texproj_assign_uvmap", text="Assign UVMap") + if props.texproj.running: + box.operator(texture_projection.MUV_TexProjProject.bl_idname, + text="Project") + + box = layout.box() + box.prop(sc, "muv_uvw_enabled", text="UVW") + if sc.muv_uvw_enabled: + row = box.row(align=True) + ops = row.operator(uvw.MUV_UVWBoxMap.bl_idname, text="Box") + ops.assign_uvmap = sc.muv_uvw_assign_uvmap + ops = row.operator(uvw.MUV_UVWBestPlanerMap.bl_idname, + text="Best Planner") + ops.assign_uvmap = sc.muv_uvw_assign_uvmap + box.prop(sc, "muv_uvw_assign_uvmap", text="Assign UVMap") |