From c1ab9b4b9c6c0226f8d7789b92efda9b0f33cfd1 Mon Sep 17 00:00:00 2001 From: Stephen Leger Date: Sat, 22 Jul 2017 13:25:28 +0200 Subject: archipack: T52120 release to official --- archipack/archipack_preset.py | 578 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 578 insertions(+) create mode 100644 archipack/archipack_preset.py (limited to 'archipack/archipack_preset.py') diff --git a/archipack/archipack_preset.py b/archipack/archipack_preset.py new file mode 100644 index 00000000..c5fe9446 --- /dev/null +++ b/archipack/archipack_preset.py @@ -0,0 +1,578 @@ +# -*- coding:utf-8 -*- + +# ##### 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: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +import bpy +import os +from bl_operators.presets import AddPresetBase +from mathutils import Vector +from bpy.props import StringProperty +from .archipack_gl import ( + ThumbHandle, Screen, GlRect, + GlPolyline, GlPolygon, GlText, GlHandle +) + + +class CruxHandle(GlHandle): + + def __init__(self, sensor_size, depth): + GlHandle.__init__(self, sensor_size, 0, True, False) + self.branch_0 = GlPolygon((1, 1, 1, 1), d=2) + self.branch_1 = GlPolygon((1, 1, 1, 1), d=2) + self.branch_2 = GlPolygon((1, 1, 1, 1), d=2) + self.branch_3 = GlPolygon((1, 1, 1, 1), d=2) + self.depth = depth + + def set_pos(self, pos_2d): + self.pos_2d = pos_2d + o = pos_2d + w = 0.5 * self.sensor_width + d = self.depth + c = d / 1.4242 + s = w - c + p0 = o + Vector((s, w)) + p1 = o + Vector((w, s)) + p2 = o + Vector((c, 0)) + p3 = o + Vector((w, -s)) + p4 = o + Vector((s, -w)) + p5 = o + Vector((0, -c)) + p6 = o + Vector((-s, -w)) + p7 = o + Vector((-w, -s)) + p8 = o + Vector((-c, 0)) + p9 = o + Vector((-w, s)) + p10 = o + Vector((-s, w)) + p11 = o + Vector((0, c)) + self.branch_0.set_pos([p11, p0, p1, p2, o]) + self.branch_1.set_pos([p2, p3, p4, p5, o]) + self.branch_2.set_pos([p5, p6, p7, p8, o]) + self.branch_3.set_pos([p8, p9, p10, p11, o]) + + @property + def pts(self): + return [self.pos_2d] + + @property + def sensor_center(self): + return self.pos_2d + + def draw(self, context, render=False): + self.render = render + self.branch_0.colour_inactive = self.colour + self.branch_1.colour_inactive = self.colour + self.branch_2.colour_inactive = self.colour + self.branch_3.colour_inactive = self.colour + self.branch_0.draw(context) + self.branch_1.draw(context) + self.branch_2.draw(context) + self.branch_3.draw(context) + + +class SeekBox(GlText, GlHandle): + """ + Text input to filter items by label + TODO: + - add cross to empty text + - get text from keyboard + """ + + def __init__(self): + GlHandle.__init__(self, 0, 0, True, False, d=2) + GlText.__init__(self, d=2) + self.sensor_width = 250 + self.pos_3d = Vector((0, 0)) + self.bg = GlRect(colour=(0, 0, 0, 0.7)) + self.frame = GlPolyline((1, 1, 1, 1), d=2) + self.frame.closed = True + self.cancel = CruxHandle(16, 4) + self.line_pos = 0 + + @property + def pts(self): + return [self.pos_3d] + + def set_pos(self, context, pos_2d): + x, ty = self.text_size(context) + w = self.sensor_width + y = 12 + pos_2d.y += y + pos_2d.x -= 0.5 * w + self.pos_2d = pos_2d.copy() + self.pos_3d = pos_2d.copy() + self.pos_3d.x += 6 + self.sensor_height = y + p0 = pos_2d + Vector((w, -0.5 * y)) + p1 = pos_2d + Vector((w, 1.5 * y)) + p2 = pos_2d + Vector((0, 1.5 * y)) + p3 = pos_2d + Vector((0, -0.5 * y)) + self.bg.set_pos([p0, p2]) + self.frame.set_pos([p0, p1, p2, p3]) + self.cancel.set_pos(pos_2d + Vector((w + 15, 0.5 * y))) + + def keyboard_entry(self, context, event): + c = event.ascii + if c: + if c == ",": + c = "." + self.label = self.label[:self.line_pos] + c + self.label[self.line_pos:] + self.line_pos += 1 + + if self.label: + if event.type == 'BACK_SPACE': + self.label = self.label[:self.line_pos - 1] + self.label[self.line_pos:] + self.line_pos -= 1 + + elif event.type == 'DEL': + self.label = self.label[:self.line_pos] + self.label[self.line_pos + 1:] + + elif event.type == 'LEFT_ARROW': + self.line_pos = (self.line_pos - 1) % (len(self.label) + 1) + + elif event.type == 'RIGHT_ARROW': + self.line_pos = (self.line_pos + 1) % (len(self.label) + 1) + + def draw(self, context): + self.bg.draw(context) + self.frame.draw(context) + GlText.draw(self, context) + self.cancel.draw(context) + + @property + def sensor_center(self): + return self.pos_3d + + +preset_paths = bpy.utils.script_paths("presets") +addons_paths = bpy.utils.script_paths("addons") + + +class PresetMenuItem(): + def __init__(self, thumbsize, preset, image=None): + name = bpy.path.display_name_from_filepath(preset) + self.preset = preset + self.handle = ThumbHandle(thumbsize, name, image, draggable=True) + self.enable = True + + def filter(self, keywords): + for key in keywords: + if key not in self.handle.label.label: + return False + return True + + def set_pos(self, context, pos): + self.handle.set_pos(context, pos) + + def check_hover(self, mouse_pos): + self.handle.check_hover(mouse_pos) + + def mouse_press(self): + if self.handle.hover: + self.handle.hover = False + self.handle.active = True + return True + return False + + def draw(self, context): + if self.enable: + self.handle.draw(context) + + +class PresetMenu(): + + keyboard_type = { + 'BACK_SPACE', 'DEL', + 'LEFT_ARROW', 'RIGHT_ARROW' + } + + def __init__(self, context, category, thumbsize=Vector((150, 100))): + self.imageList = [] + self.menuItems = [] + self.thumbsize = thumbsize + file_list = self.scan_files(category) + self.default_image = None + self.load_default_image() + for filepath in file_list: + self.make_menuitem(filepath) + self.margin = 50 + self.y_scroll = 0 + self.scroll_max = 1000 + self.spacing = Vector((25, 25)) + self.screen = Screen(self.margin) + self.mouse_pos = Vector((0, 0)) + self.bg = GlRect(colour=(0, 0, 0, 0.7)) + self.border = GlPolyline((0.7, 0.7, 0.7, 1), d=2) + self.keywords = SeekBox() + self.keywords.colour_normal = (1, 1, 1, 1) + + self.border.closed = True + self.set_pos(context) + + def load_default_image(self): + img_idx = bpy.data.images.find("missing.png") + if img_idx > -1: + self.default_image = bpy.data.images[img_idx] + self.imageList.append(self.default_image.filepath_raw) + return + dir_path = os.path.dirname(os.path.realpath(__file__)) + sub_path = "presets" + os.path.sep + "missing.png" + filepath = os.path.join(dir_path, sub_path) + if os.path.exists(filepath) and os.path.isfile(filepath): + self.default_image = bpy.data.images.load(filepath=filepath) + self.imageList.append(self.default_image.filepath_raw) + if self.default_image is None: + raise EnvironmentError("archipack/presets/missing.png not found") + + def scan_files(self, category): + file_list = [] + # load default presets + dir_path = os.path.dirname(os.path.realpath(__file__)) + sub_path = "presets" + os.path.sep + category + presets_path = os.path.join(dir_path, sub_path) + if os.path.exists(presets_path): + file_list += [presets_path + os.path.sep + f[:-3] + for f in os.listdir(presets_path) + if f.endswith('.py') and + not f.startswith('.')] + # load user def presets + for path in preset_paths: + presets_path = os.path.join(path, category) + if os.path.exists(presets_path): + file_list += [presets_path + os.path.sep + f[:-3] + for f in os.listdir(presets_path) + if f.endswith('.py') and + not f.startswith('.')] + + file_list.sort() + return file_list + + def clearImages(self): + for image in bpy.data.images: + if image.filepath_raw in self.imageList: + # image.user_clear() + bpy.data.images.remove(image, do_unlink=True) + self.imageList.clear() + + def make_menuitem(self, filepath): + """ + @TODO: + Lazy load images + """ + image = None + img_idx = bpy.data.images.find(os.path.basename(filepath) + '.png') + if img_idx > -1: + image = bpy.data.images[img_idx] + self.imageList.append(image.filepath_raw) + elif os.path.exists(filepath + '.png') and os.path.isfile(filepath + '.png'): + image = bpy.data.images.load(filepath=filepath + '.png') + self.imageList.append(image) + if image is None: + image = self.default_image + item = PresetMenuItem(self.thumbsize, filepath + '.py', image) + self.menuItems.append(item) + + def set_pos(self, context): + + x_min, x_max, y_min, y_max = self.screen.size(context) + p0, p1, p2, p3 = Vector((x_min, y_min)), Vector((x_min, y_max)), Vector((x_max, y_max)), Vector((x_max, y_min)) + self.bg.set_pos([p0, p2]) + self.border.set_pos([p0, p1, p2, p3]) + x_min += 0.5 * self.thumbsize.x + 0.5 * self.margin + x_max -= 0.5 * self.thumbsize.x + 0.5 * self.margin + y_max -= 0.5 * self.thumbsize.y + 0.5 * self.margin + y_min += 0.5 * self.margin + x = x_min + y = y_max + self.y_scroll + n_rows = 0 + + self.keywords.set_pos(context, p1 + 0.5 * (p2 - p1)) + keywords = self.keywords.label.split(" ") + + for item in self.menuItems: + if y > y_max or y < y_min: + item.enable = False + else: + item.enable = True + + # filter items by name + if len(keywords) > 0 and not item.filter(keywords): + item.enable = False + continue + + item.set_pos(context, Vector((x, y))) + x += self.thumbsize.x + self.spacing.x + if x > x_max: + n_rows += 1 + x = x_min + y -= self.thumbsize.y + self.spacing.y + + self.scroll_max = max(0, n_rows - 1) * (self.thumbsize.y + self.spacing.y) + + def draw(self, context): + self.bg.draw(context) + self.border.draw(context) + self.keywords.draw(context) + for item in self.menuItems: + item.draw(context) + + def mouse_press(self, context, event): + self.mouse_position(event) + + if self.keywords.cancel.hover: + self.keywords.label = "" + self.keywords.line_pos = 0 + self.set_pos(context) + + for item in self.menuItems: + if item.enable and item.mouse_press(): + # load item preset + return item.preset + return None + + def mouse_position(self, event): + self.mouse_pos.x, self.mouse_pos.y = event.mouse_region_x, event.mouse_region_y + + def mouse_move(self, context, event): + self.mouse_position(event) + self.keywords.check_hover(self.mouse_pos) + self.keywords.cancel.check_hover(self.mouse_pos) + for item in self.menuItems: + item.check_hover(self.mouse_pos) + + def scroll_up(self, context, event): + self.y_scroll = max(0, self.y_scroll - (self.thumbsize.y + self.spacing.y)) + self.set_pos(context) + # print("scroll_up %s" % (self.y_scroll)) + + def scroll_down(self, context, event): + self.y_scroll = min(self.scroll_max, self.y_scroll + (self.thumbsize.y + self.spacing.y)) + self.set_pos(context) + # print("scroll_down %s" % (self.y_scroll)) + + def keyboard_entry(self, context, event): + self.keywords.keyboard_entry(context, event) + self.set_pos(context) + + +class PresetMenuOperator(): + + preset_operator = StringProperty( + options={'SKIP_SAVE'}, + default="script.execute_preset" + ) + + def __init__(self): + self.menu = None + self._handle = None + + def exit(self, context): + self.menu.clearImages() + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + + def draw_handler(self, _self, context): + self.menu.draw(context) + + def modal(self, context, event): + if self.menu is None: + return {'FINISHED'} + context.area.tag_redraw() + if event.type == 'MOUSEMOVE': + self.menu.mouse_move(context, event) + elif event.type == 'WHEELUPMOUSE' or \ + (event.type == 'UP_ARROW' and event.value == 'PRESS'): + self.menu.scroll_up(context, event) + elif event.type == 'WHEELDOWNMOUSE' or \ + (event.type == 'DOWN_ARROW' and event.value == 'PRESS'): + self.menu.scroll_down(context, event) + elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE': + preset = self.menu.mouse_press(context, event) + if preset is not None: + self.exit(context) + po = self.preset_operator.split(".") + op = getattr(getattr(bpy.ops, po[0]), po[1]) + if self.preset_operator == 'script.execute_preset': + # call from preset menu + # ensure right active_object class + d = getattr(bpy.types, self.preset_subdir).datablock(context.active_object) + if d is not None: + d.auto_update = False + # print("Archipack execute_preset loading auto_update:%s" % d.auto_update) + op('INVOKE_DEFAULT', filepath=preset, menu_idname=self.bl_idname) + # print("Archipack execute_preset loaded auto_update: %s" % d.auto_update) + d.auto_update = True + else: + # call draw operator + if op.poll(): + op('INVOKE_DEFAULT', filepath=preset) + else: + print("Poll failed") + return {'FINISHED'} + elif event.ascii or ( + event.type in self.menu.keyboard_type and + event.value == 'RELEASE'): + self.menu.keyboard_entry(context, event) + elif event.type in {'RIGHTMOUSE', 'ESC'}: + self.exit(context) + return {'CANCELLED'} + + return {'RUNNING_MODAL'} + + def invoke(self, context, event): + if context.area.type == 'VIEW_3D': + + # with alt pressed on invoke, will bypass menu operator and + # call preset_operator + # allow start drawing linked copy of active object + if event.alt or event.ctrl: + po = self.preset_operator.split(".") + op = getattr(getattr(bpy.ops, po[0]), po[1]) + d = context.active_object.data + + if d is not None and self.preset_subdir in d and op.poll(): + op('INVOKE_DEFAULT') + else: + self.report({'WARNING'}, "Active object must be a " + self.preset_subdir.split("_")[1].capitalize()) + return {'CANCELLED'} + return {'FINISHED'} + + self.menu = PresetMenu(context, self.preset_subdir) + + # the arguments we pass the the callback + args = (self, context) + # Add the region OpenGL drawing callback + # draw in view space with 'POST_VIEW' and 'PRE_VIEW' + self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_handler, args, 'WINDOW', 'POST_PIXEL') + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + else: + self.report({'WARNING'}, "View3D not found, cannot show preset flinger") + return {'CANCELLED'} + + +class ArchipackPreset(AddPresetBase): + + @classmethod + def poll(cls, context): + o = context.active_object + return o is not None and \ + o.data is not None and \ + "archipack_" + cls.__name__[13:-7] in o.data + + @property + def preset_subdir(self): + return "archipack_" + self.__class__.__name__[13:-7] + + @property + def blacklist(self): + """ + properties black list for presets + may override on addon basis + """ + return [] + + @property + def preset_values(self): + blacklist = self.blacklist + blacklist.extend(bpy.types.Mesh.bl_rna.properties.keys()) + d = getattr(bpy.context.active_object.data, self.preset_subdir)[0] + props = d.rna_type.bl_rna.properties.items() + ret = [] + for prop_id, prop in props: + if prop_id not in blacklist: + if not (prop.is_hidden or prop.is_skip_save): + ret.append("d.%s" % prop_id) + return ret + + @property + def preset_defines(self): + return ["d = bpy.context.active_object.data." + self.preset_subdir + "[0]"] + + def pre_cb(self, context): + return + + def remove(self, context, filepath): + # remove preset + os.remove(filepath) + # remove thumb + os.remove(filepath[:-3] + ".png") + + def post_cb(self, context): + + if not self.remove_active: + + name = self.name.strip() + if not name: + return + + filename = self.as_filename(name) + target_path = os.path.join("presets", self.preset_subdir) + target_path = bpy.utils.user_resource('SCRIPTS', + target_path, + create=True) + + filepath = os.path.join(target_path, filename) + ".png" + + # render thumb + scene = context.scene + render = scene.render + + # save render parame + resolution_x = render.resolution_x + resolution_y = render.resolution_y + resolution_percentage = render.resolution_percentage + old_filepath = render.filepath + use_file_extension = render.use_file_extension + use_overwrite = render.use_overwrite + use_compositing = render.use_compositing + use_sequencer = render.use_sequencer + file_format = render.image_settings.file_format + color_mode = render.image_settings.color_mode + color_depth = render.image_settings.color_depth + + render.resolution_x = 150 + render.resolution_y = 100 + render.resolution_percentage = 100 + render.filepath = filepath + render.use_file_extension = True + render.use_overwrite = True + render.use_compositing = False + render.use_sequencer = False + render.image_settings.file_format = 'PNG' + render.image_settings.color_mode = 'RGBA' + render.image_settings.color_depth = '8' + bpy.ops.render.render(animation=False, write_still=True, use_viewport=False) + + # restore render params + render.resolution_x = resolution_x + render.resolution_y = resolution_y + render.resolution_percentage = resolution_percentage + render.filepath = old_filepath + render.use_file_extension = use_file_extension + render.use_overwrite = use_overwrite + render.use_compositing = use_compositing + render.use_sequencer = use_sequencer + render.image_settings.file_format = file_format + render.image_settings.color_mode = color_mode + render.image_settings.color_depth = color_depth + + return -- cgit v1.2.3