Compare commits
	
		
			No commits in common. "master" and "v3.3.2" have entirely different histories.
		
	
	
		
	
		
							
								
								
									
										29
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @ -1,34 +1,5 @@ | ||||
| # Changelog | ||||
| 
 | ||||
| 
 | ||||
| 4.2.0 | ||||
| 
 | ||||
| - added: Delete Grease pencil strokes or points view bound (selection outside of viewport region is untouched) | ||||
|   - No preset shortcut | ||||
|   <!-- - `grease_pencil.delete_points_view_bound` for points --> | ||||
|   <!-- - `grease_pencil.delete_strokes_view_bound` for strokes --> | ||||
|   - operator id_name: `grease_pencil.delete_view_bound` | ||||
|   - Added to Delete menu | ||||
|   - support multiframe | ||||
|   - Only do a dissolve (delete for full stroke) | ||||
| 
 | ||||
| 4.1.3 | ||||
| 
 | ||||
| - fixed: error in "remove stroke duplication" using file checker | ||||
| - added: `Remove Redundant GP Stroke` Operator (Not exposed, avaialbe in search with developer extras enabled). Allow to use directly without going using file checker | ||||
| 
 | ||||
| 4.1.2 | ||||
| 
 | ||||
| - fixed: Error in Draw cam overlay breaking after random undo step `Ctrl + Z` | ||||
| 
 | ||||
| 4.1.1 | ||||
| 
 | ||||
| - added: option in copy stroke world space to not bake move | ||||
| 
 | ||||
| 4.1.0 | ||||
| 
 | ||||
| - added: operator `list collection visibility conflicts` (not exposed, called from search menu with dev extras enabled in prefs) | ||||
| 
 | ||||
| 4.0.3 | ||||
| 
 | ||||
| changed: File checker doest not fix directly when clicked (also removed choice in preference): | ||||
|  | ||||
| @ -6,7 +6,6 @@ from bpy.props import (FloatProperty, | ||||
|                         StringProperty, | ||||
|                         IntProperty) | ||||
| from .. import utils | ||||
| from ..utils import is_hidden | ||||
| 
 | ||||
| ## copied from OP_key_duplicate_send | ||||
| def get_layer_list(self, context): | ||||
| @ -143,7 +142,7 @@ class GP_OT_create_empty_frames(bpy.types.Operator): | ||||
|             tgt_layers = [l for i, l in enumerate(gpl) if i == active_index - 1] | ||||
|          | ||||
|         elif self.targeted_layers == 'ALL_VISIBLE': | ||||
|             tgt_layers = [l for l in gpl if not is_hidden(l) and l != gpl.active] | ||||
|             tgt_layers = [l for l in gpl if not l.hide and l != gpl.active] | ||||
|          | ||||
|         elif self.targeted_layers == 'CHOSEN': | ||||
|             if not self.layers_enum: | ||||
| @ -221,4 +220,4 @@ def register(): | ||||
|     bpy.utils.register_class(GP_OT_create_empty_frames) | ||||
| 
 | ||||
| def unregister(): | ||||
|     bpy.utils.unregister_class(GP_OT_create_empty_frames) | ||||
|     bpy.utils.unregister_class(GP_OT_create_empty_frames) | ||||
| @ -5,14 +5,11 @@ from ..utils import (location_to_region, | ||||
|                     vector_length, | ||||
|                     draw_gp_stroke,  | ||||
|                     extrapolate_points_by_length, | ||||
|                     simple_draw_gp_stroke, | ||||
|                     is_hidden, | ||||
|                     is_locked) | ||||
|                     simple_draw_gp_stroke) | ||||
| 
 | ||||
| import bpy | ||||
| from math import degrees | ||||
| from mathutils import Vector | ||||
| 
 | ||||
| # from os.path import join, basename, exists, dirname, abspath, splitext | ||||
| 
 | ||||
| # iterate over selected layer and all/selected frame and close gaps between line extermities with a tolerance level | ||||
| @ -279,9 +276,9 @@ class GPSTK_OT_extend_lines(bpy.types.Operator): | ||||
|         if self.layer_tgt == 'ACTIVE': | ||||
|             lays = [ob.data.layers.active] | ||||
|         elif self.layer_tgt == 'SELECTED': | ||||
|             lays = [l for l in ob.data.layers if l.select and not is_hidden(l)] | ||||
|             lays = [l for l in ob.data.layers if l.select and not l.hide] | ||||
|         elif self.layer_tgt == 'ALL_VISIBLE': | ||||
|             lays = [l for l in ob.data.layers if not is_hidden(l)] | ||||
|             lays = [l for l in ob.data.layers if not l.hide] | ||||
|         else: | ||||
|             lays = [l for l in ob.data.layers if not any(x in l.name for x in ('spot', 'colo'))] | ||||
| 
 | ||||
| @ -340,9 +337,9 @@ class GPSTK_OT_change_closeline_length(bpy.types.Operator): | ||||
|         if self.layer_tgt == 'ACTIVE': | ||||
|             lays = [ob.data.layers.active] | ||||
|         elif self.layer_tgt == 'SELECTED': | ||||
|             lays = [l for l in ob.data.layers if l.select and not is_hidden(l)] | ||||
|             lays = [l for l in ob.data.layers if l.select and not l.hide] | ||||
|         elif self.layer_tgt == 'ALL_VISIBLE': | ||||
|             lays = [l for l in ob.data.layers if not is_hidden(l)] | ||||
|             lays = [l for l in ob.data.layers if not l.hide] | ||||
|         else: | ||||
|             lays = [l for l in ob.data.layers if not any(x in l.name for x in ('spot', 'colo'))] | ||||
| 
 | ||||
| @ -378,7 +375,7 @@ class GPSTK_OT_comma_finder(bpy.types.Operator): | ||||
|     def execute(self, context): | ||||
|         ct = 0 | ||||
|         ob = context.object | ||||
|         lays = [l for l in ob.data.layers if not is_hidden(l) and not is_locked(l)] | ||||
|         lays = [l for l in ob.data.layers if not l.hide and not l.lock] | ||||
|         for l in lays: | ||||
|             if not l.current_frame():continue | ||||
|             for s in l.current_frame().drawing.strokes: | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| ## GP clipboard : Copy/Cut/Paste Grease Pencil strokes to/from OS clipboard across layers and blends | ||||
| ## View3D > Toolbar > Gpencil > GP clipboard | ||||
| ## in 4.2, existed in standalone scripts: https://github.com/Pullusb/GP_clipboard | ||||
| ## in 4.2- existed in standalone scripts: https://github.com/Pullusb/GP_clipboard | ||||
| 
 | ||||
| import bpy | ||||
| import mathutils | ||||
| @ -9,7 +9,6 @@ import json | ||||
| from time import time | ||||
| from operator import itemgetter | ||||
| from itertools import groupby | ||||
| from .utils import is_locked, is_hidden | ||||
| 
 | ||||
| def convertAttr(Attr): | ||||
|     '''Convert given value to a Json serializable format''' | ||||
| @ -140,7 +139,7 @@ def copycut_strokes(layers=None, copy=True, keep_empty=True): | ||||
|     #     color = gp.palettes.active.colors.active.name | ||||
|     if not layers: | ||||
|         # by default all visible layers | ||||
|         layers = [l for l in gpl if not is_hidden(l) and not is_locked(l)] # [] | ||||
|         layers = [l for l in gpl if not l.hide and not l.lock] # [] | ||||
|     if not isinstance(layers, list): | ||||
|         # if a single layer object is send put in a list | ||||
|         layers = [layers] | ||||
| @ -236,7 +235,7 @@ def copy_all_strokes(layers=None): | ||||
| 
 | ||||
|     if not layers: | ||||
|         # by default all visible layers | ||||
|         layers = [l for l in gpl if not is_hidden(l) and not is_locked(l)]# include locked ? | ||||
|         layers = [l for l in gpl if not l.hide and not l.lock]# include locked ? | ||||
|     if not isinstance(layers, list): | ||||
|         # if a single layer object is send put in a list | ||||
|         layers = [layers] | ||||
| @ -276,7 +275,7 @@ def copy_all_strokes_in_frame(frame=None, layers=None, obj=None, | ||||
| 
 | ||||
|     if not layers: | ||||
|         # by default all visible layers | ||||
|         layers = [l for l in gpl if not is_hidden(l) and not is_locked(l)] # include locked ? | ||||
|         layers = [l for l in gpl if not l.hide and not l.lock] # include locked ? | ||||
|     if not isinstance(layers, list): | ||||
|         # if a single layer object is send put in a list | ||||
|         layers = [layers] | ||||
| @ -478,9 +477,6 @@ class GPCLIP_OT_copy_multi_strokes(bpy.types.Operator): | ||||
|     def poll(cls, context): | ||||
|         return context.object and context.object.type == 'GREASEPENCIL' | ||||
| 
 | ||||
|     bake_moves : bpy.props.BoolProperty(name='Bake Move', default=True, | ||||
|         description='Copy every frame where object has moved, else copy only existing frames') | ||||
| 
 | ||||
|     radius : bpy.props.BoolProperty(name='radius', default=True, | ||||
|         description='Dump point radius attribute (already skipped if at default value)') | ||||
|     opacity : bpy.props.BoolProperty(name='opacity', default=True, | ||||
| @ -505,7 +501,6 @@ class GPCLIP_OT_copy_multi_strokes(bpy.types.Operator): | ||||
|         layout=self.layout | ||||
|         layout.use_property_split = True | ||||
|         col = layout.column() | ||||
|         col.prop(self, 'bake_moves') | ||||
|         col.label(text='Keep following point attributes:') | ||||
|         col.prop(self, 'radius') | ||||
|         col.prop(self, 'opacity') | ||||
| @ -516,6 +511,7 @@ class GPCLIP_OT_copy_multi_strokes(bpy.types.Operator): | ||||
|         return | ||||
| 
 | ||||
|     def execute(self, context): | ||||
|         bake_moves = True | ||||
|         skip_empty_frame = False | ||||
| 
 | ||||
|         org_frame = context.scene.frame_current | ||||
| @ -525,15 +521,15 @@ class GPCLIP_OT_copy_multi_strokes(bpy.types.Operator): | ||||
|         #ct = check_radius() | ||||
|         layerdic = {} | ||||
| 
 | ||||
|         layerpool = [l for l in gpl if not is_hidden(l) and l.select] # and not is_locked(l) | ||||
|         layerpool = [l for l in gpl if not l.hide and l.select] # and not l.lock | ||||
|         if not layerpool: | ||||
|             self.report({'ERROR'}, 'No layers selected in GP dopesheet (needs to be visible and selected to be copied)\nHint: Changing active layer reset selection to active only') | ||||
|             return {"CANCELLED"} | ||||
|              | ||||
|         if not self.bake_moves: # copy only drawed frames as is. | ||||
|         if not bake_moves: # copy only drawed frames as is. | ||||
|             for l in layerpool: | ||||
|                 if not l.frames: | ||||
|                     continue # skip empty layers | ||||
|                     continue# skip empty layers | ||||
| 
 | ||||
|                 frame_dic = {} | ||||
|                 for f in l.frames: | ||||
|  | ||||
| @ -1,173 +0,0 @@ | ||||
| import bpy | ||||
| 
 | ||||
| from . import utils | ||||
| from bpy_extras.view3d_utils import location_3d_to_region_2d | ||||
| 
 | ||||
| def is_stroke_in_view(stroke, matrix, region, region_3d): | ||||
|     ## for optimization, use first and last | ||||
|     region_width = region.width | ||||
|     region_height = region.height | ||||
|     point_list = [stroke.points[0], stroke.points[-1]] | ||||
|     # point_list = stroke.points # whole points | ||||
|      | ||||
|     coord_list = [matrix @ point.position for point in point_list] | ||||
| 
 | ||||
|     for coord in coord_list: | ||||
|         # Convert 3D coordinates to 2D screen coordinates | ||||
|         screen_coord = location_3d_to_region_2d(region, region_3d, coord) | ||||
|         if screen_coord is None: | ||||
|             continue | ||||
|         # Check if the point is within the viewport bounds | ||||
|         if 0 <= screen_coord.x <= region_width and 0 <= screen_coord.y <= region_height: | ||||
|             ## one point in view, in view | ||||
|             return True | ||||
|      | ||||
|     return False | ||||
| 
 | ||||
| class GP_OT_delete_view_bound(bpy.types.Operator): | ||||
|     bl_idname = "grease_pencil.delete_view_bound" | ||||
|     bl_label = "Delete View Bound" | ||||
|     bl_description = "Delete all selected strokes / points only if there are in view (current viewport region)\ | ||||
|         \nnote: does not work in multiframe yet)" | ||||
|     bl_options = {'REGISTER', 'UNDO'} | ||||
| 
 | ||||
|     # def invoke(self, context, event): | ||||
|     ## TODO: add scope (default to automatic) | ||||
|     ## TODO: add option for normal delete (currenly only dissolve), probably another operator. | ||||
| 
 | ||||
|     @classmethod | ||||
|     def poll(cls, context): | ||||
|         return context.mode == 'EDIT_GREASE_PENCIL' | ||||
| 
 | ||||
|     def execute(self, context): | ||||
|         gp = context.grease_pencil | ||||
|         if not gp: | ||||
|             self.report({'ERROR'}, "No Grease Pencil object found") | ||||
|             return {'CANCELLED'} | ||||
|          | ||||
|         ob = context.object | ||||
|         if not ob or ob.type != 'GREASEPENCIL': | ||||
|             self.report({'ERROR'}, "Active object is not a Grease Pencil object") | ||||
|             return {'CANCELLED'} | ||||
| 
 | ||||
|         select_mode = context.scene.tool_settings.gpencil_selectmode_edit | ||||
|         matrix_world = ob.matrix_world.copy() | ||||
| 
 | ||||
|         ## only visibile and unlocked layer in active GP object | ||||
|         layers = [l for l in gp.layers if not l.hide and not l.lock] | ||||
| 
 | ||||
|         region = context.region | ||||
|         region_3d = context.space_data.region_3d         | ||||
| 
 | ||||
|         removed_ct = 0 | ||||
|         avoid_ct = 0 | ||||
|         for layer in layers: | ||||
|             if context.scene.tool_settings.use_grease_pencil_multi_frame_editing: | ||||
|                 ## multiframe, affect all selected frames and active frame | ||||
|                 target_frames = [f for f in layer.frames if f.select or f == layer.current_frame()] | ||||
|             else: | ||||
|                 ## Only active frame | ||||
|                 current = layer.current_frame() | ||||
|                 if not current: | ||||
|                     continue | ||||
|                 target_frames = [current] | ||||
| 
 | ||||
|             for frame in target_frames: | ||||
|                 drawing = frame.drawing | ||||
|                 if not drawing or not drawing.strokes: | ||||
|                     continue | ||||
| 
 | ||||
|                 stroke_indices_to_delete = [] | ||||
|                 ## List comprehention version (For stroke mode only) | ||||
|                 # stroke_indices_to_delete = [sidx for sidx, stroke in enumerate(drawing.strokes) if stroke.select and is_stroke_in_view(stroke, matrix_world, region, region_3d)] | ||||
|                 for sid, stroke in enumerate(drawing.strokes): | ||||
|                     if not stroke.select: | ||||
|                         continue | ||||
|                      | ||||
|                     # Check if the stroke is within the view bounds | ||||
|                     if not is_stroke_in_view(stroke, matrix_world, region, region_3d): | ||||
|                         avoid_ct += 1 | ||||
|                         continue | ||||
| 
 | ||||
|                     ## No need for further check if we are in stroke mod (direct delete) | ||||
|                     if select_mode == 'STROKE': | ||||
|                         stroke_indices_to_delete.append(sid) | ||||
|                         removed_ct += 1 | ||||
|                         continue | ||||
|                      | ||||
|                     ## --- Handle points --- | ||||
| 
 | ||||
|                     ## remove points, only Dissolve for now. | ||||
|                     select_count = len([p for p in stroke.points if p.select]) | ||||
|                     if select_count == len(stroke.points): | ||||
|                         ## Mark as full delete and go next | ||||
|                         stroke_indices_to_delete.append(sid) | ||||
|                         continue | ||||
| 
 | ||||
| 
 | ||||
|                     ## Make a copy of deselected points attributes | ||||
|                     point_attrs = [] | ||||
|                     for p in stroke.points: | ||||
|                         if p.select: | ||||
|                             continue | ||||
|                         point_attrs.append([p.position.copy(), p.rotation, p.radius, p.opacity]) | ||||
| 
 | ||||
|                     ## remove as many points as select_count | ||||
|                     stroke.remove_points(select_count) | ||||
|                      | ||||
|                     ## re-assign attributes in order | ||||
|                     for i, p in enumerate(stroke.points): | ||||
|                         if i < len(point_attrs): | ||||
|                             p.position = point_attrs[i][0] | ||||
|                             p.rotation = point_attrs[i][1] | ||||
|                             p.radius = point_attrs[i][2] | ||||
|                             p.opacity = point_attrs[i][3] | ||||
|                         p.select = False  # Deselect all points after processing | ||||
|                      | ||||
|                     removed_ct += 1 | ||||
|                     ## End of stroke loop (here either part if the stroke is deleted or marked for deletion ) | ||||
| 
 | ||||
|                 if stroke_indices_to_delete: | ||||
|                     # print(f'layer {layer.name} : delete {len(stroke_indices_to_delete)} strokes') | ||||
|                     drawing.remove_strokes(indices=stroke_indices_to_delete) | ||||
| 
 | ||||
|         if removed_ct and avoid_ct: | ||||
|             self.report({'INFO'}, f"Skipped {avoid_ct} out of view") | ||||
|         elif not removed_ct and not avoid_ct: | ||||
|             self.report({'WARNING'}, "Nothing to Delete") | ||||
|         elif not removed_ct and avoid_ct: | ||||
|             self.report({'WARNING'}, "All selected strokes are out of view") | ||||
| 
 | ||||
|         return {'FINISHED'} | ||||
| 
 | ||||
| 
 | ||||
| def draw_delete_view_bound_ui(self, context): | ||||
|     layout = self.layout | ||||
|     layout.operator('grease_pencil.delete_view_bound', text='Delete View bound', icon='LOCKVIEW_ON') # RESTRICT_VIEW_ON | ||||
| 
 | ||||
| 
 | ||||
| classes = ( | ||||
|     # GP_OT_delete_strokes_view_bound, | ||||
|     # GP_OT_delete_points_view_bound, | ||||
|     GP_OT_delete_view_bound, | ||||
| ) | ||||
| 
 | ||||
| def register(): | ||||
|     if bpy.app.background: | ||||
|         return | ||||
| 
 | ||||
|     for cl in classes: | ||||
|         bpy.utils.register_class(cl) | ||||
|     bpy.types.VIEW3D_MT_edit_greasepencil_delete.append(draw_delete_view_bound_ui) | ||||
| 
 | ||||
|     ## make scene property for empty key preservation and bake movement for layers... | ||||
|     # register_keymaps() | ||||
| 
 | ||||
| def unregister(): | ||||
|     if bpy.app.background: | ||||
|         return | ||||
| 
 | ||||
|     bpy.types.VIEW3D_MT_edit_greasepencil_delete.remove(draw_delete_view_bound_ui) | ||||
|     # unregister_keymaps() | ||||
|     for cl in reversed(classes): | ||||
|         bpy.utils.unregister_class(cl) | ||||
| @ -11,7 +11,6 @@ from bpy_extras.view3d_utils import region_2d_to_location_3d, region_2d_to_vecto | ||||
| location_3d_to_region_2d, region_2d_to_origin_3d, region_2d_to_location_3d | ||||
| from time import time | ||||
| from math import pi, cos, sin | ||||
| from .utils import is_locked, is_hidden | ||||
| 
 | ||||
| 
 | ||||
| def get_gp_mat(gp, name, set_active=False): | ||||
| @ -441,7 +440,7 @@ class GPTB_OT_eraser(Operator): | ||||
|          | ||||
|         t0 = time() | ||||
|         gp_mats = gp.data.materials | ||||
|         gp_layers = [l for l in gp.data.layers if not is_locked(l) or is_hidden(l)] | ||||
|         gp_layers = [l for l in gp.data.layers if not l.lock or l.hide] | ||||
|         self.gp_frames = [l.current_frame() for l in gp_layers] | ||||
|         ''' | ||||
|         points_data = [(s, f, gp_mats[s.material_index]) for f in gp_frames for s in f.drawing.strokes] | ||||
|  | ||||
| @ -2,7 +2,6 @@ import bpy | ||||
| import os | ||||
| from pathlib import Path | ||||
| import numpy as np | ||||
| 
 | ||||
| from . import utils | ||||
| 
 | ||||
| from bpy.props import (BoolProperty, | ||||
| @ -10,85 +9,30 @@ from bpy.props import (BoolProperty, | ||||
|                         CollectionProperty, | ||||
|                         StringProperty) | ||||
| 
 | ||||
| def remove_stroke_exact_duplications(apply=True, verbose=True, select=False): | ||||
| def remove_stroke_exact_duplications(apply=True): | ||||
|     '''Remove accidental stroke duplication (points exactly in the same place) | ||||
|     :apply: Remove the duplication instead of just listing dupes | ||||
|     :select: Select the duplicated strokes instead of removing them (disabled by apply) | ||||
|     return number of duplication found/deleted | ||||
|     ''' | ||||
|     # TODO: Add additional check of material (even if unlikely to happen, better to avoid false positive) | ||||
|     # TODO: add additional check of material (even if unlikely to happen) | ||||
|     ct = 0 | ||||
|     if verbose: | ||||
|         print('\nRemove redundant strokes in GP frames...') | ||||
|     gp_datas = [gp for gp in bpy.data.grease_pencils_v3] | ||||
|     gp_datas = [gp for gp in bpy.data.grease_pencils] | ||||
|     for gp in gp_datas: | ||||
|         for l in gp.layers: | ||||
|             for f in l.frames: | ||||
|                 stroke_list = [] | ||||
|                 idx_to_delete = [] | ||||
| 
 | ||||
|                 for idx, s in enumerate(f.drawing.strokes): | ||||
|                     point_list = [p.position.copy() for p in s.points] | ||||
|                 for s in reversed(f.drawing.strokes): | ||||
|                      | ||||
|                     point_list = [p.position for p in s.points] | ||||
|                      | ||||
|                     if point_list in stroke_list: | ||||
|                         ct += 1 | ||||
|                         idx_to_delete.append(idx) | ||||
|                         if not apply and select: | ||||
|                             s.select = True | ||||
|                         if apply: | ||||
|                             # Remove redundancy | ||||
|                             f.drawing.strokes.remove(s) | ||||
|                     else: | ||||
|                         stroke_list.append(point_list) | ||||
|                         if not apply and select: | ||||
|                             s.select = False | ||||
| 
 | ||||
|                 if idx_to_delete: | ||||
|                     if verbose: | ||||
|                         print(f"{gp.name} > {l.name} > frame {f.frame_number}: {len(idx_to_delete)} strokes") | ||||
|                     if apply: | ||||
|                         # Remove redundancy (carefull, passing an empty list delete all strokes) | ||||
|                         f.drawing.remove_strokes(indices=idx_to_delete) | ||||
|     return ct | ||||
| 
 | ||||
| class GPTB_OT_remove_stroke_duplication(bpy.types.Operator): | ||||
|     bl_idname = "gp.remove_stroke_duplication" | ||||
|     bl_label = "Remove Redundant GP Stroke" | ||||
|     bl_description = "Within every frame, remove every strokes that are exactly superposed with a previous one" | ||||
|     bl_options = {"REGISTER", "UNDO"} | ||||
| 
 | ||||
|     apply : bpy.props.BoolProperty(name="Remove Stroke", default=True, | ||||
|                 description="Remove the duplication, else list only", | ||||
|                 options={'SKIP_SAVE'}) | ||||
| 
 | ||||
|     select : bpy.props.BoolProperty(name="Select Duplicated Strokes", default=False) | ||||
| 
 | ||||
|     def invoke(self, context, event): | ||||
|         # self.file_dump = event.ctrl | ||||
|         return context.window_manager.invoke_props_dialog(self) # , width=400 | ||||
|         # return self.execute(context) | ||||
| 
 | ||||
|     def draw(self, context): | ||||
|         layout=self.layout | ||||
|         layout.use_property_split = True | ||||
|         col = layout.column() | ||||
|         col.prop(self, 'apply') | ||||
| 
 | ||||
|         row=col.row(align=True) | ||||
|         row.prop(self, 'select') | ||||
|         row.enabled = not self.apply | ||||
|         if not self.apply and not self.select: | ||||
|             col.label(text="Only list in console and report number of duplications", icon='INFO') | ||||
| 
 | ||||
|     def execute(self, context): | ||||
|         ct = remove_stroke_exact_duplications(apply=self.apply, select=self.select, verbose=True) | ||||
|         if ct > 0: | ||||
|             if self.apply: | ||||
|                 self.report({'INFO'}, f'Removed {ct} strokes duplications') | ||||
|             else: | ||||
|                 self.report({'INFO'}, f'Found {ct} strokes duplications') | ||||
|         else: | ||||
|             self.report({'INFO'}, 'No stroke duplication found') | ||||
| 
 | ||||
|         return {'FINISHED'} | ||||
|      | ||||
| 
 | ||||
| class GPTB_OT_file_checker(bpy.types.Operator): | ||||
|     bl_idname = "gp.file_checker" | ||||
|     bl_label = "Check File" | ||||
| @ -322,7 +266,6 @@ class GPTB_OT_file_checker(bpy.types.Operator): | ||||
|                         bpy.context.scene.tool_settings.lock_object_mode = False | ||||
| 
 | ||||
|         if fix.remove_redundant_strokes: | ||||
|             print('removing redundant strokes') | ||||
|             ct = remove_stroke_exact_duplications(apply=apply) | ||||
|             if ct > 0: | ||||
|                 mess = f'Removed {ct} strokes duplications' if apply else f'Found {ct} strokes duplications' | ||||
| @ -330,7 +273,7 @@ class GPTB_OT_file_checker(bpy.types.Operator): | ||||
| 
 | ||||
|         # ## Set onion skin filter to 'All type' | ||||
|         # fix_kf_type = 0  | ||||
|         # for gp in bpy.data.grease_pencils_v3:#from data | ||||
|         # for gp in bpy.data.grease_pencils:#from data | ||||
|         #     if not gp.is_annotation: | ||||
|         #         if gp.onion_keyframe_type != 'ALL':  | ||||
|         #             gp.onion_keyframe_type = 'ALL' | ||||
| @ -706,98 +649,6 @@ class GPTB_OT_list_object_visibility_conflicts(bpy.types.Operator): | ||||
|     def execute(self, context): | ||||
|         return {'FINISHED'} | ||||
| 
 | ||||
| ## -- List collection visibility conflicts | ||||
| 
 | ||||
| # class GPTB_PG_collection_visibility(bpy.types.PropertyGroup): | ||||
| #     """Property group to handle collection visibility""" | ||||
| #     is_hidden: BoolProperty( | ||||
| #         name="Hide in Viewport", | ||||
| #         description="Toggle collection visibility in viewport", | ||||
| #         get=lambda self: self.get("is_hidden", False), | ||||
| #         set=lambda self, value: self.set_visibility(value) | ||||
| #     ) | ||||
| 
 | ||||
| #     collection_name: StringProperty(name="Collection Name") | ||||
| 
 | ||||
| def get_collection_children_recursive(col, cols=None) -> list: | ||||
|     '''return a list of all the child collections | ||||
|     and their subcollections in the passed collection''' | ||||
| 
 | ||||
|     cols = cols or []  | ||||
|     for sub in col.children: | ||||
|         if sub not in cols: | ||||
|             cols.append(sub) | ||||
|         if len(sub.children): | ||||
|             cols = get_collection_children_recursive(sub, cols) | ||||
|     return cols | ||||
| 
 | ||||
| class GPTB_OT_list_collection_visibility_conflicts(bpy.types.Operator): | ||||
|     bl_idname = "gp.list_collection_visibility_conflicts" | ||||
|     bl_label = "List Collection Visibility Conflicts" | ||||
|     bl_description = "List collection visibility conflicts, when viewport and render have different values" | ||||
|     bl_options = {"REGISTER"} | ||||
| 
 | ||||
|     # visibility_items: CollectionProperty(type=GPTB_PG_collection_visibility) | ||||
|     show_filter : bpy.props.EnumProperty( | ||||
|         name="View Filter", | ||||
|         description="Filter collections based on their exclusion status", | ||||
|         items=( | ||||
|                 ('ALL', "All", "Show all collections", 0), | ||||
|                 ('NOT_EXCLUDED', "Not Excluded", "Show collections that are not excluded", 1), | ||||
|                 ('EXCLUDED', "Excluded", "Show collections that are excluded", 2) | ||||
|             ), | ||||
|         default='NOT_EXCLUDED') | ||||
| 
 | ||||
|     def invoke(self, context, event): | ||||
|         ## get all viewlayer collections | ||||
|         vcols = get_collection_children_recursive(context.view_layer.layer_collection) | ||||
|         vcols = list(set(vcols)) # ensure no duplicates | ||||
| 
 | ||||
|         ## Store collection with conflicts | ||||
|         # layer_collection.is_visible against render visibility ? | ||||
|         ## Do not list currently excluded collections | ||||
|         self.conflict_collections = [vc for vc in vcols if not (vc.hide_viewport == vc.collection.hide_viewport == vc.collection.hide_render)] | ||||
|         self.included_collection = [vc for vc in self.conflict_collections if not vc.exclude] | ||||
|         self.excluded_collection = [vc for vc in self.conflict_collections if vc.exclude] | ||||
| 
 | ||||
|         return context.window_manager.invoke_props_dialog(self, width=250) | ||||
| 
 | ||||
|     def draw(self, context): | ||||
|         layout = self.layout | ||||
|         layout.prop(self, 'show_filter', expand=True) | ||||
| 
 | ||||
|         # Add sync buttons at the top | ||||
|         row = layout.row(align=False) | ||||
|         # TODO: Add "set all from" ops on collection (optionnal) | ||||
|         # row.label(text="Sync All Visibility From:") | ||||
|         # row.operator("gp.sync_visibility_from_viewlayer", text="", icon='HIDE_OFF') | ||||
|         # row.operator("gp.sync_visibility_from_viewport", text="", icon='RESTRICT_VIEW_OFF') | ||||
|         # row.operator("gp.sync_visibility_from_render", text="", icon='RESTRICT_RENDER_OFF') | ||||
|         layout.separator() | ||||
| 
 | ||||
|         if self.show_filter == 'ALL': | ||||
|             vl_collections = self.conflict_collections | ||||
|         elif self.show_filter == 'EXCLUDED': | ||||
|             vl_collections = self.excluded_collection | ||||
|         elif self.show_filter == 'NOT_EXCLUDED': | ||||
|             vl_collections = self.included_collection | ||||
| 
 | ||||
|         col = layout.column() | ||||
|         for vlcol in vl_collections: | ||||
|             row = col.row(align=False) | ||||
|             row.label(text=vlcol.name) | ||||
| 
 | ||||
|             # Viewlayer collection settings | ||||
|             row.prop(vlcol, "exclude", text="", emboss=False) | ||||
|             row.prop(vlcol, "hide_viewport", text="", emboss=False) | ||||
| 
 | ||||
|             # Direct collection properties | ||||
|             row.prop(vlcol.collection, 'hide_viewport', text='', emboss=False) | ||||
|             row.prop(vlcol.collection, 'hide_render', text='', emboss=False) | ||||
| 
 | ||||
|     def execute(self, context): | ||||
|         return {'FINISHED'} | ||||
| 
 | ||||
| ## not exposed in UI, Check is performed in Check file (can be called in popped menu) | ||||
| class GPTB_OT_list_modifier_visibility(bpy.types.Operator): | ||||
|     bl_idname = "gp.list_modifier_visibility" | ||||
| @ -845,13 +696,11 @@ GPTB_OT_sync_visibility_from_render, | ||||
| GPTB_OT_sync_visibible_to_render, | ||||
| GPTB_PG_object_visibility, | ||||
| GPTB_OT_list_object_visibility_conflicts, | ||||
| GPTB_OT_list_collection_visibility_conflicts, | ||||
| GPTB_OT_list_modifier_visibility, | ||||
| GPTB_OT_copy_string_to_clipboard, | ||||
| GPTB_OT_copy_multipath_clipboard, | ||||
| GPTB_OT_file_checker, | ||||
| GPTB_OT_links_checker, | ||||
| GPTB_OT_remove_stroke_duplication, | ||||
| ) | ||||
| 
 | ||||
| def register(): | ||||
|  | ||||
| @ -12,7 +12,7 @@ from .utils import (location_to_region, region_to_location) | ||||
| ## Do not work on multiple object | ||||
| def batch_flat_reproject(obj, proj_type='VIEW', all_strokes=True, restore_frame=False): | ||||
|     '''Reproject | ||||
|     :all_stroke: affect hidden, locked layers | ||||
|     :all_stroke: affect hided, locked layers | ||||
|     ''' | ||||
| 
 | ||||
|     if restore_frame: | ||||
|  | ||||
| @ -2,6 +2,7 @@ import bpy | ||||
| from mathutils import Vector | ||||
| from . import utils | ||||
| 
 | ||||
| 
 | ||||
| class GPTB_OT_create_follow_path_curve(bpy.types.Operator): | ||||
|     bl_idname = "object.create_follow_path_curve" | ||||
|     bl_label = "Create Follow Path Curve" | ||||
|  | ||||
| @ -212,14 +212,9 @@ class GPTB_OT_draw_cam(Operator): | ||||
|             # Swap to it, unhide if necessary and hide previous  | ||||
|             context.scene.camera = maincam | ||||
|              | ||||
|             ## Hide cam object | ||||
| 
 | ||||
|             # Ensure at lviewport viz is active to ensure visibility and refresh state | ||||
|             ## hide cam object  | ||||
|             drawcam.hide_viewport = True | ||||
|             maincam.hide_viewport = False | ||||
|             # drawcam.hide_viewport = False # Not absolutely needed | ||||
| 
 | ||||
|             drawcam.hide_set(True) | ||||
|             maincam.hide_set(False) | ||||
| 
 | ||||
|         ## if in main camera GO to drawcam | ||||
|         elif context.scene.camera.name not in ('draw_cam', 'obj_cam'): | ||||
| @ -272,12 +267,8 @@ class GPTB_OT_draw_cam(Operator): | ||||
| 
 | ||||
|             ## hide cam object  | ||||
|             context.scene.camera = drawcam | ||||
|             # Ensure viewport viz is active to ensure visibility and refresh state | ||||
|             drawcam.hide_viewport = False | ||||
|             maincam.hide_viewport = False | ||||
|             ## Set viewlayer visibility | ||||
|             drawcam.hide_set(False) | ||||
|             maincam.hide_set(True) | ||||
|             maincam.hide_viewport = True | ||||
|              | ||||
|             if created and drawcam.name == 'obj_cam': # Go in camera view | ||||
|                 context.region_data.view_perspective = 'CAMERA' | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import bpy | ||||
| from .utils import get_addon_prefs, is_locked, is_hidden | ||||
| from .utils import get_addon_prefs | ||||
| from bpy.props import BoolProperty ,EnumProperty ,StringProperty | ||||
| 
 | ||||
| class GPTB_OT_jump_gp_keyframe(bpy.types.Operator): | ||||
| @ -45,15 +45,15 @@ class GPTB_OT_jump_gp_keyframe(bpy.types.Operator): | ||||
|             return {"CANCELLED"} | ||||
| 
 | ||||
|         if self.target == 'ACTIVE': | ||||
|             gpl = [l for l in context.object.data.layers if l.select and not is_hidden(l)] | ||||
|             gpl = [l for l in context.object.data.layers if l.select and not l.hide] | ||||
|             if not context.object.data.layers.active in gpl: | ||||
|                 gpl.append(context.object.data.layers.active)    | ||||
|          | ||||
|         elif self.target == 'VISIBLE': | ||||
|             gpl = [l for l in context.object.data.layers if not is_hidden(l)] | ||||
|             gpl = [l for l in context.object.data.layers if not l.hide] | ||||
|          | ||||
|         elif self.target == 'ACCESSIBLE': | ||||
|             gpl = [l for l in context.object.data.layers if not is_hidden(l) and not is_locked(l)] | ||||
|             gpl = [l for l in context.object.data.layers if not l.hide and not l.lock] | ||||
|              | ||||
|         if self.keyframe_type != 'NONE': | ||||
|             # use shortcut choice override | ||||
|  | ||||
| @ -4,7 +4,7 @@ import mathutils | ||||
| from mathutils import Vector, Matrix, geometry | ||||
| from bpy_extras import view3d_utils | ||||
| from time import time | ||||
| from .utils import get_gp_draw_plane, location_to_region, region_to_location, is_locked, is_hidden | ||||
| from .utils import get_gp_draw_plane, location_to_region, region_to_location | ||||
| 
 | ||||
| class GP_OT_pick_closest_layer(Operator): | ||||
|     bl_idname = "gp.pick_closest_layer" | ||||
| @ -76,7 +76,7 @@ class GP_OT_pick_closest_layer(Operator): | ||||
|         self.point_pair = [] | ||||
|         if context.scene.tool_settings.use_grease_pencil_multi_frame_editing: | ||||
|             for layer in gp.layers: | ||||
|                 if is_hidden(layer): | ||||
|                 if layer.hide: | ||||
|                     continue | ||||
|                 for f in layer.frames: | ||||
|                     if not f.select: | ||||
| @ -89,9 +89,9 @@ class GP_OT_pick_closest_layer(Operator): | ||||
|                         self.point_pair += [(Vector((*location_to_region(mat @ p.position), 0)), layer) for p in s.points] | ||||
| 
 | ||||
|         else: | ||||
|             # [s for l in gp.layers if not is_locked(l) and not is_hidden(l) for s in l.current_frame().stokes] | ||||
|             # [s for l in gp.layers if not l.lock and not l.hide for s in l.current_frame().stokes] | ||||
|             for layer in gp.layers: | ||||
|                 if is_hidden(layer) or not layer.current_frame(): | ||||
|                 if layer.hide or not layer.current_frame(): | ||||
|                     continue | ||||
|                 for s in layer.current_frame().drawing.strokes: | ||||
|                     if self.stroke_filter == 'STROKE' and not self.ob.data.materials[s.material_index].grease_pencil.show_stroke: | ||||
|  | ||||
| @ -4,12 +4,7 @@ import mathutils | ||||
| from mathutils import Vector, Matrix, geometry | ||||
| from bpy_extras import view3d_utils | ||||
| from time import time | ||||
| from .utils import (get_gp_draw_plane, | ||||
|                     location_to_region, | ||||
|                     region_to_location, | ||||
|                     is_locked, | ||||
|                     is_hidden) | ||||
| 
 | ||||
| from .utils import get_gp_draw_plane, location_to_region, region_to_location | ||||
| 
 | ||||
| ### passing by 2D projection | ||||
| def get_3d_coord_on_drawing_plane_from_2d(context, co): | ||||
| @ -84,7 +79,7 @@ class GP_OT_pick_closest_material(Operator): | ||||
| 
 | ||||
|         if context.scene.tool_settings.use_grease_pencil_multi_frame_editing: | ||||
|             for l in self.gp.layers: | ||||
|                 if is_hidden(l):# is_locked(l) or  | ||||
|                 if l.hide:# l.lock or  | ||||
|                     continue | ||||
|                 for f in l.frames: | ||||
|                     if not f.select: | ||||
| @ -93,9 +88,9 @@ class GP_OT_pick_closest_material(Operator): | ||||
|                         self.stroke_list.append(s) | ||||
| 
 | ||||
|         else: | ||||
|             # [s for l in self.gp.layers if not is_locked(l) and not is_hidden(l) for s in l.current_frame().stokes] | ||||
|             # [s for l in self.gp.layers if not l.lock and not l.hide for s in l.current_frame().stokes] | ||||
|             for l in self.gp.layers: | ||||
|                 if is_hidden(l) or not l.current_frame():# is_locked(l) or  | ||||
|                 if l.hide or not l.current_frame():# l.lock or  | ||||
|                     continue | ||||
|                 for s in l.current_frame().drawing.strokes: | ||||
|                     self.stroke_list.append(s) | ||||
| @ -240,7 +235,7 @@ class GP_OT_pick_closest_material(Operator): | ||||
| 
 | ||||
|         if context.scene.tool_settings.use_grease_pencil_multi_frame_editing: | ||||
|             for l in gp.layers: | ||||
|                 if is_hidden(l):# is_locked(l) or  | ||||
|                 if l.hide:# l.lock or  | ||||
|                     continue | ||||
|                 for f in l.frames: | ||||
|                     if not f.select: | ||||
| @ -249,9 +244,9 @@ class GP_OT_pick_closest_material(Operator): | ||||
|                         self.stroke_list.append(s) | ||||
| 
 | ||||
|         else: | ||||
|             # [s for l in gp.layers if not is_locked(l) and not is_hidden(l) for s in l.current_frame().stokes] | ||||
|             # [s for l in gp.layers if not l.lock and not l.hide for s in l.current_frame().stokes] | ||||
|             for l in gp.layers: | ||||
|                 if is_hidden(l) or not l.current_frame():# is_locked(l) or  | ||||
|                 if l.hide or not l.current_frame():# l.lock or  | ||||
|                     continue | ||||
|                 for s in l.current_frame().drawing.strokes: | ||||
|                     self.stroke_list.append(s) | ||||
|  | ||||
| @ -166,7 +166,7 @@ class GPTB_OT_import_obj_palette(Operator): | ||||
|          | ||||
|         # unlink objects and their gp data | ||||
|         for src_ob in linked_objs: | ||||
|             bpy.data.grease_pencils_v3.remove(src_ob.data) | ||||
|             bpy.data.grease_pencils.remove(src_ob.data) | ||||
|          | ||||
| 
 | ||||
|         return {"FINISHED"} | ||||
|  | ||||
| @ -1,13 +1,11 @@ | ||||
| import bpy | ||||
| import mathutils | ||||
| import numpy as np | ||||
| 
 | ||||
| from mathutils import Matrix, Vector | ||||
| from math import pi | ||||
| import numpy as np | ||||
| from time import time | ||||
| from mathutils.geometry import intersect_line_plane | ||||
| from . import utils | ||||
| from .utils import is_hidden, is_locked | ||||
| from mathutils.geometry import intersect_line_plane | ||||
| 
 | ||||
| def get_scale_matrix(scale): | ||||
|     # recreate a neutral mat scale | ||||
| @ -17,6 +15,35 @@ def get_scale_matrix(scale): | ||||
|     matscale = matscale_x @ matscale_y @ matscale_z | ||||
|     return matscale | ||||
| 
 | ||||
| ''' | ||||
| ## Old reproject method using Operators: | ||||
| omode = bpy.context.mode | ||||
| 
 | ||||
| if all_strokes: | ||||
|     layers_state = [[l, l.hide, l.lock, l.lock_frame] for l in obj.data.layers] | ||||
|     for l in obj.data.layers: | ||||
|         l.hide = False | ||||
|         l.lock = False | ||||
|         l.lock_frame = False | ||||
| bpy.ops.object.mode_set(mode='EDIT') | ||||
| 
 | ||||
| 
 | ||||
| for fnum in frame_list: | ||||
|     bpy.context.scene.frame_current = fnum | ||||
|     bpy.ops.gpencil.select_all(action='SELECT') | ||||
|     bpy.ops.gpencil.reproject(type=proj_type) # 'INVOKE_DEFAULT' | ||||
|     bpy.ops.gpencil.select_all(action='DESELECT') | ||||
| 
 | ||||
| # restore | ||||
| if all_strokes: | ||||
|     for layer, hide, lock, lock_frame in layers_state: | ||||
|         layer.hide = hide | ||||
|         layer.lock = lock | ||||
|         layer.lock_frame = lock_frame | ||||
| 
 | ||||
| bpy.ops.object.mode_set(mode=omode) | ||||
| ''' | ||||
| 
 | ||||
| def batch_reproject(obj, proj_type='VIEW', all_strokes=True, restore_frame=False): | ||||
|     '''Reproject - ops method | ||||
|     :all_stroke: affect hidden, locked layers | ||||
| @ -46,7 +73,7 @@ def batch_reproject(obj, proj_type='VIEW', all_strokes=True, restore_frame=False | ||||
|             if not all_strokes: | ||||
|                 if not layer.select: | ||||
|                     continue | ||||
|                 if is_hidden(layer) or is_locked(layer): | ||||
|                 if layer.hide or layer.lock: | ||||
|                     continue | ||||
| 
 | ||||
|             frame = next((f for f in layer.frames if f.frame_number == i), None) | ||||
|  | ||||
| @ -6,7 +6,7 @@ Blender addon - Various tool to help with grease pencil in animation productions | ||||
| 
 | ||||
| **[Download latest](https://git.autourdeminuit.com/autour_de_minuit/gp_toolbox/archive/master.zip)** | ||||
| 
 | ||||
| **[Download for Blender 4.2 and below from release page](https://git.autourdeminuit.com/autour_de_minuit/gp_toolbox/releases)** | ||||
| **[Download for Blender 4.2 and below](https://git.autourdeminuit.com/autour_de_minuit/gp_toolbox/archive/v3.3.0.zip)** | ||||
| 
 | ||||
| **[Demo video](https://www.youtube.com/watch?v=Htgao_uPWNs)** | ||||
| 
 | ||||
|  | ||||
| @ -4,7 +4,7 @@ Blender addon - Boîte à outils de grease pencil pour la production d'animation | ||||
| 
 | ||||
| **[Télécharger la dernière version](https://git.autourdeminuit.com/autour_de_minuit/gp_toolbox/archive/master.zip)** | ||||
| 
 | ||||
| **[Téléchargement pour Blender 4.2 ou inférieur depuis la page des releases](https://git.autourdeminuit.com/autour_de_minuit/gp_toolbox/releases)** | ||||
| **[Télécharger pour Blender 4.2 ou inférieure](https://git.autourdeminuit.com/autour_de_minuit/gp_toolbox/archive/v3.3.0.zip)** | ||||
| 
 | ||||
| **[Demo video](https://www.youtube.com/watch?v=Htgao_uPWNs)** | ||||
| 
 | ||||
|  | ||||
| @ -4,7 +4,7 @@ bl_info = { | ||||
| "name": "GP toolbox", | ||||
| "description": "Tool set for Grease Pencil in animation production", | ||||
| "author": "Samuel Bernou, Christophe Seux", | ||||
| "version": (4, 2, 0), | ||||
| "version": (4, 0, 3), | ||||
| "blender": (4, 3, 0), | ||||
| "location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties", | ||||
| "warning": "", | ||||
| @ -47,7 +47,6 @@ from . import OP_layer_namespace | ||||
| from . import OP_pseudo_tint | ||||
| from . import OP_follow_curve | ||||
| from . import OP_material_move_to_layer | ||||
| from . import OP_delete_viewbound | ||||
| # from . import OP_eraser_brush | ||||
| # from . import TOOL_eraser_brush | ||||
| from . import handler_draw_cam | ||||
| @ -806,7 +805,6 @@ addon_modules = ( | ||||
|     OP_layer_nav, | ||||
|     OP_follow_curve, | ||||
|     OP_material_move_to_layer, | ||||
|     OP_delete_viewbound, | ||||
|     # OP_eraser_brush, | ||||
|     # TOOL_eraser_brush, # experimental eraser brush | ||||
|     handler_draw_cam, | ||||
|  | ||||
| @ -11,12 +11,10 @@ from bpy.props import ( | ||||
| from .OP_cursor_snap_canvas import cursor_follow_update | ||||
| from .OP_layer_manager import layer_name_build | ||||
| 
 | ||||
| 
 | ||||
| ## Obsolete: Gpv3 has no edit line color anymore | ||||
| # def change_edit_lines_opacity(self, context): | ||||
| #     for gp in bpy.data.grease_pencils: | ||||
| #         if not gp.is_annotation: | ||||
| #             gp.edit_line_color[3]=self.edit_lines_opacity | ||||
| def change_edit_lines_opacity(self, context): | ||||
|     for gp in bpy.data.grease_pencils: | ||||
|         if not gp.is_annotation: | ||||
|             gp.edit_line_color[3]=self.edit_lines_opacity | ||||
| 
 | ||||
| 
 | ||||
| def update_layer_name(self, context): | ||||
|  | ||||
							
								
								
									
										24
									
								
								utils.py
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								utils.py
									
									
									
									
									
								
							| @ -144,7 +144,7 @@ def object_derived_get(ob, scene): | ||||
| 
 | ||||
| 
 | ||||
| # ----------------- | ||||
| # region Bmesh | ||||
| ### Bmesh | ||||
| # ----------------- | ||||
| 
 | ||||
| def link_vert(v,ordered_vert) : | ||||
| @ -219,7 +219,7 @@ def gp_stroke_to_bmesh(strokes): | ||||
| 
 | ||||
| 
 | ||||
| # ----------------- | ||||
| # region GP Drawing | ||||
| ### GP Drawing | ||||
| # ----------------- | ||||
| 
 | ||||
| def layer_active_index(gpl): | ||||
| @ -393,7 +393,7 @@ def remapping(value, leftMin, leftMax, rightMin, rightMax): | ||||
|     return rightMin + (valueScaled * rightSpan) | ||||
| 
 | ||||
| # ----------------- | ||||
| # region GP funcs | ||||
| ### GP funcs | ||||
| # ----------------- | ||||
| 
 | ||||
| def get_gp_draw_plane(obj=None, orient=None): | ||||
| @ -753,7 +753,7 @@ def copy_frame_at(source_frame, layer, frame_number): | ||||
|     # print(f"frame copy execution time: {frame_copy_end - frame_copy_start} seconds") # time_dbg | ||||
| 
 | ||||
| # ----------------- | ||||
| # region Vector utils 3d | ||||
| ### Vector utils 3d | ||||
| # ----------------- | ||||
| 
 | ||||
| def matrix_transform(coords, matrix): | ||||
| @ -814,7 +814,7 @@ def extrapolate_points_by_length(a,b, length): | ||||
|     return b + (ab.normalized() * length) | ||||
| 
 | ||||
| # ----------------- | ||||
| # region Vector utils 2d | ||||
| ### Vector utils 2d | ||||
| # ----------------- | ||||
| 
 | ||||
| 
 | ||||
| @ -867,7 +867,7 @@ def midpoint_2d(p1, p2): | ||||
| 
 | ||||
| 
 | ||||
| # ----------------- | ||||
| # region Collection management | ||||
| ### Collection management | ||||
| # ----------------- | ||||
| 
 | ||||
| def set_collection(ob, collection, unlink=True) : | ||||
| @ -903,7 +903,7 @@ def set_collection(ob, collection, unlink=True) : | ||||
| 
 | ||||
| 
 | ||||
| # ----------------- | ||||
| # region Path utils | ||||
| ### Path utils | ||||
| # ----------------- | ||||
| 
 | ||||
| def get_addon_prefs(): | ||||
| @ -1048,7 +1048,7 @@ def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'): | ||||
|     bpy.context.window_manager.popup_menu(draw, title = _title, icon = _icon) | ||||
| 
 | ||||
| # ----------------- | ||||
| # region UI utils | ||||
| ### UI utils | ||||
| # ----------------- | ||||
| 
 | ||||
| def refresh_areas(): | ||||
| @ -1164,7 +1164,7 @@ def draw_kmi(km, kmi, layout): | ||||
|         #         layout.context_pointer_set("keymap", km) | ||||
| 
 | ||||
| # ----------------- | ||||
| # region linking utility | ||||
| ### linking utility | ||||
| # ----------------- | ||||
| 
 | ||||
| def link_objects_in_blend(filepath, obj_name_list, link=True): | ||||
| @ -1192,7 +1192,7 @@ def check_objects_in_blend(filepath, avoid_camera=True): | ||||
| 
 | ||||
| 
 | ||||
| # ----------------- | ||||
| # region props handling | ||||
| ### props handling | ||||
| # ----------------- | ||||
| 
 | ||||
| def iterate_selector(zone, attr, state, info_attr = None, active_access='active'): | ||||
| @ -1256,7 +1256,7 @@ def iterate_active_layer(gpd, state): | ||||
|         # return info, bottom | ||||
| 
 | ||||
| # ----------------- | ||||
| # region Curve handle | ||||
| ### Curve handle | ||||
| # ----------------- | ||||
| 
 | ||||
| def create_curve(location=(0,0,0), direction=(1,0,0), name='curve_path', enter_edit=True, context=None): | ||||
| @ -1404,7 +1404,7 @@ def create_follow_path_constraint(ob, curve, follow_curve=False, use_fixed_locat | ||||
| 
 | ||||
| 
 | ||||
| # ----------------- | ||||
| # region Object | ||||
| ### Object | ||||
| # ----------------- | ||||
| 
 | ||||
| def go_edit_mode(ob, context=None): | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user