# ##### BEGIN GPL LICENSE BLOCK ##### # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # ##### END GPL LICENSE BLOCK ##### import bpy import mathutils import numpy as np from .dump_anything import (Dumper, Loader, np_dump_collection, np_load_collection) from .bl_datablock import BlDatablock # GPencil data api is structured as it follow: # GP-Object --> GP-Layers --> GP-Frames --> GP-Strokes --> GP-Stroke-Points STROKE_POINT = [ 'co', 'pressure', 'strength', 'uv_factor', 'uv_rotation' ] STROKE = [ "aspect", "display_mode", "end_cap_mode", "hardness", "line_width", "material_index", "start_cap_mode", "uv_rotation", "uv_scale", "uv_translation", "vertex_color_fill", ] if bpy.app.version[1] >= 91: STROKE.append('use_cyclic') else: STROKE.append('draw_cyclic') if bpy.app.version[1] >= 83: STROKE_POINT.append('vertex_color') def dump_stroke(stroke): """ Dump a grease pencil stroke to a dict :param stroke: target grease pencil stroke :type stroke: bpy.types.GPencilStroke :return: dict """ assert(stroke) dumper = Dumper() dumper.include_filter = [ "aspect", "display_mode", "draw_cyclic", "end_cap_mode", "hardeness", "line_width", "material_index", "start_cap_mode", "uv_rotation", "uv_scale", "uv_translation", "vertex_color_fill", ] dumped_stroke = dumper.dump(stroke) # Stoke points p_count = len(stroke.points) dumped_stroke['p_count'] = p_count dumped_stroke['points'] = np_dump_collection(stroke.points, STROKE_POINT) # TODO: uv_factor, uv_rotation return dumped_stroke def load_stroke(stroke_data, stroke): """ Load a grease pencil stroke from a dict :param stroke_data: dumped grease pencil stroke :type stroke_data: dict :param stroke: target grease pencil stroke :type stroke: bpy.types.GPencilStroke """ assert(stroke and stroke_data) stroke.points.add(stroke_data["p_count"]) np_load_collection(stroke_data['points'], stroke.points, STROKE_POINT) # HACK: Temporary fix to trigger a BKE_gpencil_stroke_geometry_update to # fix fill issues stroke.uv_scale = stroke_data["uv_scale"] def dump_frame(frame): """ Dump a grease pencil frame to a dict :param frame: target grease pencil stroke :type frame: bpy.types.GPencilFrame :return: dict """ assert(frame) dumped_frame = dict() dumped_frame['frame_number'] = frame.frame_number dumped_frame['strokes'] = np_dump_collection(frame.strokes, STROKE) dumped_frame['strokes_points'] = [] for stroke in frame.strokes: dumped_frame['strokes_points'].append(dump_stroke(stroke)) return dumped_frame def load_frame(frame_data, frame): """ Load a grease pencil frame from a dict :param frame_data: source grease pencil frame :type frame_data: dict :param frame: target grease pencil stroke :type frame: bpy.types.GPencilFrame """ assert(frame and frame_data) for stroke_data in frame_data['strokes_points']: target_stroke = frame.strokes.new() load_stroke(stroke_data, target_stroke) np_load_collection(frame_data['strokes'], frame.strokes, STROKE) def dump_layer(layer): """ Dump a grease pencil layer :param layer: target grease pencil stroke :type layer: bpy.types.GPencilFrame """ assert(layer) dumper = Dumper() dumper.include_filter = [ 'info', 'opacity', 'channel_color', 'color', # 'thickness', #TODO: enabling only for annotation 'tint_color', 'tint_factor', 'vertex_paint_opacity', 'line_change', 'use_onion_skinning', 'use_annotation_onion_skinning', 'annotation_onion_before_range', 'annotation_onion_after_range', 'annotation_onion_before_color', 'annotation_onion_after_color', 'pass_index', # 'viewlayer_render', 'blend_mode', 'hide', 'annotation_hide', 'lock', # 'lock_frame', # 'lock_material', # 'use_mask_layer', 'use_lights', 'use_solo_mode', 'select', 'show_points', 'show_in_front', # 'parent', # 'parent_type', # 'parent_bone', # 'matrix_inverse', ] if layer.id_data.is_annotation: dumper.include_filter.append('thickness') dumped_layer = dumper.dump(layer) dumped_layer['frames'] = [] for frame in layer.frames: dumped_layer['frames'].append(dump_frame(frame)) return dumped_layer def load_layer(layer_data, layer): """ Load a grease pencil layer from a dict :param layer_data: source grease pencil layer data :type layer_data: dict :param layer: target grease pencil stroke :type layer: bpy.types.GPencilFrame """ # TODO: take existing data in account loader = Loader() loader.load(layer, layer_data) for frame_data in layer_data["frames"]: target_frame = layer.frames.new(frame_data['frame_number']) load_frame(frame_data, target_frame) class BlGpencil(BlDatablock): bl_id = "grease_pencils" bl_class = bpy.types.GreasePencil bl_check_common = False bl_icon = 'GREASEPENCIL' bl_reload_parent = False def construct(data: dict) -> object: return bpy.data.grease_pencils.new(data["name"]) def load(data: dict, datablock: object): target.materials.clear() if "materials" in data.keys(): for mat in data['materials']: target.materials.append(bpy.data.materials[mat]) loader = Loader() loader.load(target, data) # TODO: reuse existing layer for layer in target.layers: target.layers.remove(layer) if "layers" in data.keys(): for layer in data["layers"]: layer_data = data["layers"].get(layer) # if layer not in target.layers.keys(): target_layer = target.layers.new(data["layers"][layer]["info"]) # else: # target_layer = target.layers[layer] # target_layer.clear() load_layer(layer_data, target_layer) target.layers.update() def dump(datablock: object) -> dict: assert(instance) dumper = Dumper() dumper.depth = 2 dumper.include_filter = [ 'materials', 'name', 'zdepth_offset', 'stroke_thickness_space', 'pixel_factor', 'stroke_depth_order' ] data = dumper.dump(instance) data['layers'] = {} for layer in instance.layers: data['layers'][layer.info] = dump_layer(layer) data["active_layers"] = instance.layers.active.info data["eval_frame"] = bpy.context.scene.frame_current return data @staticmethod def resolve_deps(datablock: object) -> [object]: deps = [] for material in datablock.materials: deps.append(material) return deps def layer_changed(self): return self.instance.layers.active.info != self.data["active_layers"] def frame_changed(self): return bpy.context.scene.frame_current != self.data["eval_frame"] def diff(self): if self.layer_changed() \ or self.frame_changed() \ or bpy.context.mode == 'OBJECT' \ or self.preferences.sync_flags.sync_during_editmode: return super().diff() else: return False