From c1ab9b4b9c6c0226f8d7789b92efda9b0f33cfd1 Mon Sep 17 00:00:00 2001 From: Stephen Leger <stephen@3dservices.ch> Date: Sat, 22 Jul 2017 13:25:28 +0200 Subject: [PATCH] archipack: T52120 release to official --- archipack/__init__.py | 646 ++++ archipack/archipack_2d.py | 893 ++++++ archipack/archipack_autoboolean.py | 678 ++++ archipack/archipack_door.py | 1847 +++++++++++ archipack/archipack_fence.py | 1782 +++++++++++ archipack/archipack_floor.py | 1190 +++++++ archipack/archipack_gl.py | 1228 +++++++ archipack/archipack_handle.py | 178 + archipack/archipack_keymaps.py | 108 + archipack/archipack_manipulator.py | 2446 ++++++++++++++ archipack/archipack_object.py | 237 ++ archipack/archipack_polylib.py | 2274 +++++++++++++ archipack/archipack_preset.py | 578 ++++ archipack/archipack_reference_point.py | 368 +++ archipack/archipack_rendering.py | 529 +++ archipack/archipack_slab.py | 1505 +++++++++ archipack/archipack_snap.py | 309 ++ archipack/archipack_stair.py | 2849 +++++++++++++++++ archipack/archipack_truss.py | 380 +++ archipack/archipack_wall.py | 137 + archipack/archipack_wall2.py | 2220 +++++++++++++ archipack/archipack_window.py | 2098 ++++++++++++ archipack/bitarray.py | 97 + archipack/bmesh_utils.py | 249 ++ archipack/icons/archipack.png | Bin 0 -> 1364 bytes archipack/icons/detect.png | Bin 0 -> 281 bytes archipack/icons/door.png | Bin 0 -> 414 bytes archipack/icons/fence.png | Bin 0 -> 1779 bytes archipack/icons/floor.png | Bin 0 -> 1457 bytes archipack/icons/polygons.png | Bin 0 -> 242 bytes archipack/icons/selection.png | Bin 0 -> 1021 bytes archipack/icons/slab.png | Bin 0 -> 1620 bytes archipack/icons/stair.png | Bin 0 -> 1486 bytes archipack/icons/truss.png | Bin 0 -> 1462 bytes archipack/icons/union.png | Bin 0 -> 1102 bytes archipack/icons/wall.png | Bin 0 -> 637 bytes archipack/icons/window.png | Bin 0 -> 579 bytes archipack/materialutils.py | 169 + archipack/panel.py | 715 +++++ .../presets/archipack_door/160x200_dual.png | Bin 0 -> 10252 bytes .../presets/archipack_door/160x200_dual.py | 23 + .../presets/archipack_door/400x240_garage.png | Bin 0 -> 10492 bytes .../presets/archipack_door/400x240_garage.py | 23 + archipack/presets/archipack_door/80x200.png | Bin 0 -> 7840 bytes archipack/presets/archipack_door/80x200.py | 23 + .../presets/archipack_fence/glass_panels.png | Bin 0 -> 7106 bytes .../presets/archipack_fence/glass_panels.py | 67 + .../archipack_fence/inox_glass_concrete.png | Bin 0 -> 7835 bytes .../archipack_fence/inox_glass_concrete.py | 64 + archipack/presets/archipack_fence/metal.png | Bin 0 -> 10234 bytes archipack/presets/archipack_fence/metal.py | 67 + .../presets/archipack_fence/metal_glass.png | Bin 0 -> 9582 bytes .../presets/archipack_fence/metal_glass.py | 67 + archipack/presets/archipack_fence/wood.png | Bin 0 -> 13183 bytes archipack/presets/archipack_fence/wood.py | 67 + .../archipack_floor/herringbone_50x10.png | Bin 0 -> 11148 bytes .../archipack_floor/herringbone_50x10.py | 34 + .../archipack_floor/herringbone_p_50x10.png | Bin 0 -> 10924 bytes .../archipack_floor/herringbone_p_50x10.py | 34 + .../presets/archipack_floor/parquet_15x3.png | Bin 0 -> 13445 bytes .../presets/archipack_floor/parquet_15x3.py | 34 + .../presets/archipack_floor/planks_200x20.png | Bin 0 -> 11644 bytes .../presets/archipack_floor/planks_200x20.py | 34 + .../presets/archipack_floor/tiles_15x15.png | Bin 0 -> 12939 bytes .../presets/archipack_floor/tiles_15x15.py | 34 + .../presets/archipack_floor/tiles_60x30.png | Bin 0 -> 11379 bytes .../presets/archipack_floor/tiles_60x30.py | 34 + .../archipack_floor/tiles_hex_10x10.png | Bin 0 -> 13663 bytes .../archipack_floor/tiles_hex_10x10.py | 34 + .../tiles_l+ms_30x30_15x15.png | Bin 0 -> 12511 bytes .../archipack_floor/tiles_l+ms_30x30_15x15.py | 34 + .../archipack_floor/tiles_l+s_30x30_15x15.png | Bin 0 -> 11631 bytes .../archipack_floor/tiles_l+s_30x30_15x15.py | 34 + .../archipack_stair/i_wood_over_concrete.png | Bin 0 -> 15606 bytes .../archipack_stair/i_wood_over_concrete.py | 117 + .../archipack_stair/l_wood_over_concrete.png | Bin 0 -> 18279 bytes .../archipack_stair/l_wood_over_concrete.py | 155 + .../archipack_stair/o_wood_over_concrete.png | Bin 0 -> 13886 bytes .../archipack_stair/o_wood_over_concrete.py | 136 + .../archipack_stair/u_wood_over_concrete.png | Bin 0 -> 18165 bytes .../archipack_stair/u_wood_over_concrete.py | 155 + .../archipack_window/120x110_flat_2.png | Bin 0 -> 8410 bytes .../archipack_window/120x110_flat_2.py | 50 + .../120x110_flat_2_elliptic.png | Bin 0 -> 8593 bytes .../120x110_flat_2_elliptic.py | 58 + .../120x110_flat_2_oblique.png | Bin 0 -> 7969 bytes .../120x110_flat_2_oblique.py | 50 + .../archipack_window/120x110_flat_2_round.png | Bin 0 -> 8571 bytes .../archipack_window/120x110_flat_2_round.py | 58 + .../archipack_window/180x110_flat_3.png | Bin 0 -> 9492 bytes .../archipack_window/180x110_flat_3.py | 50 + .../archipack_window/180x210_flat_3.png | Bin 0 -> 10314 bytes .../archipack_window/180x210_flat_3.py | 50 + .../archipack_window/180x210_rail_2.png | Bin 0 -> 9362 bytes .../archipack_window/180x210_rail_2.py | 50 + .../archipack_window/240x210_rail_3.png | Bin 0 -> 10360 bytes .../archipack_window/240x210_rail_3.py | 50 + .../presets/archipack_window/80x80_flat_1.png | Bin 0 -> 7291 bytes .../presets/archipack_window/80x80_flat_1.py | 50 + .../archipack_window/80x80_flat_1_circle.png | Bin 0 -> 6914 bytes .../archipack_window/80x80_flat_1_circle.py | 58 + archipack/presets/missing.png | Bin 0 -> 3874 bytes archipack/pyqtree.py | 187 ++ 103 files changed, 27691 insertions(+) create mode 100644 archipack/__init__.py create mode 100644 archipack/archipack_2d.py create mode 100644 archipack/archipack_autoboolean.py create mode 100644 archipack/archipack_door.py create mode 100644 archipack/archipack_fence.py create mode 100644 archipack/archipack_floor.py create mode 100644 archipack/archipack_gl.py create mode 100644 archipack/archipack_handle.py create mode 100644 archipack/archipack_keymaps.py create mode 100644 archipack/archipack_manipulator.py create mode 100644 archipack/archipack_object.py create mode 100644 archipack/archipack_polylib.py create mode 100644 archipack/archipack_preset.py create mode 100644 archipack/archipack_reference_point.py create mode 100644 archipack/archipack_rendering.py create mode 100644 archipack/archipack_slab.py create mode 100644 archipack/archipack_snap.py create mode 100644 archipack/archipack_stair.py create mode 100644 archipack/archipack_truss.py create mode 100644 archipack/archipack_wall.py create mode 100644 archipack/archipack_wall2.py create mode 100644 archipack/archipack_window.py create mode 100644 archipack/bitarray.py create mode 100644 archipack/bmesh_utils.py create mode 100644 archipack/icons/archipack.png create mode 100644 archipack/icons/detect.png create mode 100644 archipack/icons/door.png create mode 100644 archipack/icons/fence.png create mode 100644 archipack/icons/floor.png create mode 100644 archipack/icons/polygons.png create mode 100644 archipack/icons/selection.png create mode 100644 archipack/icons/slab.png create mode 100644 archipack/icons/stair.png create mode 100644 archipack/icons/truss.png create mode 100644 archipack/icons/union.png create mode 100644 archipack/icons/wall.png create mode 100644 archipack/icons/window.png create mode 100644 archipack/materialutils.py create mode 100644 archipack/panel.py create mode 100644 archipack/presets/archipack_door/160x200_dual.png create mode 100644 archipack/presets/archipack_door/160x200_dual.py create mode 100644 archipack/presets/archipack_door/400x240_garage.png create mode 100644 archipack/presets/archipack_door/400x240_garage.py create mode 100644 archipack/presets/archipack_door/80x200.png create mode 100644 archipack/presets/archipack_door/80x200.py create mode 100644 archipack/presets/archipack_fence/glass_panels.png create mode 100644 archipack/presets/archipack_fence/glass_panels.py create mode 100644 archipack/presets/archipack_fence/inox_glass_concrete.png create mode 100644 archipack/presets/archipack_fence/inox_glass_concrete.py create mode 100644 archipack/presets/archipack_fence/metal.png create mode 100644 archipack/presets/archipack_fence/metal.py create mode 100644 archipack/presets/archipack_fence/metal_glass.png create mode 100644 archipack/presets/archipack_fence/metal_glass.py create mode 100644 archipack/presets/archipack_fence/wood.png create mode 100644 archipack/presets/archipack_fence/wood.py create mode 100644 archipack/presets/archipack_floor/herringbone_50x10.png create mode 100644 archipack/presets/archipack_floor/herringbone_50x10.py create mode 100644 archipack/presets/archipack_floor/herringbone_p_50x10.png create mode 100644 archipack/presets/archipack_floor/herringbone_p_50x10.py create mode 100644 archipack/presets/archipack_floor/parquet_15x3.png create mode 100644 archipack/presets/archipack_floor/parquet_15x3.py create mode 100644 archipack/presets/archipack_floor/planks_200x20.png create mode 100644 archipack/presets/archipack_floor/planks_200x20.py create mode 100644 archipack/presets/archipack_floor/tiles_15x15.png create mode 100644 archipack/presets/archipack_floor/tiles_15x15.py create mode 100644 archipack/presets/archipack_floor/tiles_60x30.png create mode 100644 archipack/presets/archipack_floor/tiles_60x30.py create mode 100644 archipack/presets/archipack_floor/tiles_hex_10x10.png create mode 100644 archipack/presets/archipack_floor/tiles_hex_10x10.py create mode 100644 archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.png create mode 100644 archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.py create mode 100644 archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.png create mode 100644 archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.py create mode 100644 archipack/presets/archipack_stair/i_wood_over_concrete.png create mode 100644 archipack/presets/archipack_stair/i_wood_over_concrete.py create mode 100644 archipack/presets/archipack_stair/l_wood_over_concrete.png create mode 100644 archipack/presets/archipack_stair/l_wood_over_concrete.py create mode 100644 archipack/presets/archipack_stair/o_wood_over_concrete.png create mode 100644 archipack/presets/archipack_stair/o_wood_over_concrete.py create mode 100644 archipack/presets/archipack_stair/u_wood_over_concrete.png create mode 100644 archipack/presets/archipack_stair/u_wood_over_concrete.py create mode 100644 archipack/presets/archipack_window/120x110_flat_2.png create mode 100644 archipack/presets/archipack_window/120x110_flat_2.py create mode 100644 archipack/presets/archipack_window/120x110_flat_2_elliptic.png create mode 100644 archipack/presets/archipack_window/120x110_flat_2_elliptic.py create mode 100644 archipack/presets/archipack_window/120x110_flat_2_oblique.png create mode 100644 archipack/presets/archipack_window/120x110_flat_2_oblique.py create mode 100644 archipack/presets/archipack_window/120x110_flat_2_round.png create mode 100644 archipack/presets/archipack_window/120x110_flat_2_round.py create mode 100644 archipack/presets/archipack_window/180x110_flat_3.png create mode 100644 archipack/presets/archipack_window/180x110_flat_3.py create mode 100644 archipack/presets/archipack_window/180x210_flat_3.png create mode 100644 archipack/presets/archipack_window/180x210_flat_3.py create mode 100644 archipack/presets/archipack_window/180x210_rail_2.png create mode 100644 archipack/presets/archipack_window/180x210_rail_2.py create mode 100644 archipack/presets/archipack_window/240x210_rail_3.png create mode 100644 archipack/presets/archipack_window/240x210_rail_3.py create mode 100644 archipack/presets/archipack_window/80x80_flat_1.png create mode 100644 archipack/presets/archipack_window/80x80_flat_1.py create mode 100644 archipack/presets/archipack_window/80x80_flat_1_circle.png create mode 100644 archipack/presets/archipack_window/80x80_flat_1_circle.py create mode 100644 archipack/presets/missing.png create mode 100644 archipack/pyqtree.py diff --git a/archipack/__init__.py b/archipack/__init__.py new file mode 100644 index 000000000..79ac9879f --- /dev/null +++ b/archipack/__init__.py @@ -0,0 +1,646 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- + +bl_info = { + 'name': 'Archipack', + 'description': 'Architectural objects and 2d polygons detection from unordered splines', + 'author': 's-leger', + 'license': 'GPL', + 'deps': 'shapely', + 'version': (1, 2, 6), + 'blender': (2, 7, 8), + 'location': 'View3D > Tools > Create > Archipack', + 'warning': '', + 'wiki_url': 'https://github.com/s-leger/archipack/wiki', + 'tracker_url': 'https://github.com/s-leger/archipack/issues', + 'link': 'https://github.com/s-leger/archipack', + 'support': 'COMMUNITY', + 'category': 'Add Mesh' + } + +import os + +if "bpy" in locals(): + import importlib as imp + imp.reload(archipack_snap) + imp.reload(archipack_manipulator) + imp.reload(archipack_reference_point) + imp.reload(archipack_autoboolean) + imp.reload(archipack_door) + imp.reload(archipack_window) + imp.reload(archipack_stair) + imp.reload(archipack_wall) + imp.reload(archipack_wall2) + imp.reload(archipack_slab) + imp.reload(archipack_fence) + imp.reload(archipack_truss) + imp.reload(archipack_floor) + imp.reload(archipack_rendering) + try: + imp.reload(archipack_polylib) + HAS_POLYLIB = True + except: + HAS_POLYLIB = False + pass + + print("archipack: reload ready") +else: + from . import archipack_snap + from . import archipack_manipulator + from . import archipack_reference_point + from . import archipack_autoboolean + from . import archipack_door + from . import archipack_window + from . import archipack_stair + from . import archipack_wall + from . import archipack_wall2 + from . import archipack_slab + from . import archipack_fence + from . import archipack_truss + from . import archipack_floor + from . import archipack_rendering + try: + """ + polylib depends on shapely + raise ImportError when not meet + """ + from . import archipack_polylib + HAS_POLYLIB = True + except: + print("archipack: shapely not found, using built in modules only") + HAS_POLYLIB = False + pass + + print("archipack: ready") + +# noinspection PyUnresolvedReferences +import bpy +# noinspection PyUnresolvedReferences +from bpy.types import ( + Panel, WindowManager, PropertyGroup, + AddonPreferences, Menu + ) +from bpy.props import ( + EnumProperty, PointerProperty, + StringProperty, BoolProperty, + IntProperty, FloatProperty, FloatVectorProperty + ) + +from bpy.utils import previews +icons_collection = {} + + +# ---------------------------------------------------- +# Addon preferences +# ---------------------------------------------------- + +def update_panel(self, context): + try: + bpy.utils.unregister_class(TOOLS_PT_Archipack_PolyLib) + bpy.utils.unregister_class(TOOLS_PT_Archipack_Tools) + bpy.utils.unregister_class(TOOLS_PT_Archipack_Create) + except: + pass + prefs = context.user_preferences.addons[__name__].preferences + TOOLS_PT_Archipack_PolyLib.bl_category = prefs.tools_category + bpy.utils.register_class(TOOLS_PT_Archipack_PolyLib) + TOOLS_PT_Archipack_Tools.bl_category = prefs.tools_category + bpy.utils.register_class(TOOLS_PT_Archipack_Tools) + TOOLS_PT_Archipack_Create.bl_category = prefs.create_category + bpy.utils.register_class(TOOLS_PT_Archipack_Create) + + +class Archipack_Pref(AddonPreferences): + bl_idname = __name__ + + tools_category = StringProperty( + name="Tools", + description="Choose a name for the category of the Tools panel", + default="Tools", + update=update_panel + ) + create_category = StringProperty( + name="Create", + description="Choose a name for the category of the Create panel", + default="Create", + update=update_panel + ) + create_submenu = BoolProperty( + name="Use Sub-menu", + description="Put Achipack's object into a sub menu (shift+a)", + default=True + ) + max_style_draw_tool = BoolProperty( + name="Draw a wall use 3dsmax style", + description="Reverse clic / release cycle for Draw a wall", + default=True + ) + # Arrow sizes (world units) + arrow_size = FloatProperty( + name="Arrow", + description="Manipulators arrow size (blender units)", + default=0.05 + ) + # Handle area size (pixels) + handle_size = IntProperty( + name="Handle", + description="Manipulators handle sensitive area size (pixels)", + min=2, + default=10 + ) + # Font sizes and basic colour scheme + feedback_size_main = IntProperty( + name="Main", + description="Main title font size (pixels)", + min=2, + default=16 + ) + feedback_size_title = IntProperty( + name="Title", + description="Tool name font size (pixels)", + min=2, + default=14 + ) + feedback_size_shortcut = IntProperty( + name="Shortcut", + description="Shortcuts font size (pixels)", + min=2, + default=11 + ) + feedback_shortcut_area = FloatVectorProperty( + name="Background Shortcut", + description="Shortcut area background color", + subtype='COLOR_GAMMA', + default=(0, 0.4, 0.6, 0.2), + size=4, + min=0, max=1 + ) + feedback_title_area = FloatVectorProperty( + name="Background Main", + description="Title area background color", + subtype='COLOR_GAMMA', + default=(0, 0.4, 0.6, 0.5), + size=4, + min=0, max=1 + ) + feedback_colour_main = FloatVectorProperty( + name="Font Main", + description="Title color", + subtype='COLOR_GAMMA', + default=(0.95, 0.95, 0.95, 1.0), + size=4, + min=0, max=1 + ) + feedback_colour_key = FloatVectorProperty( + name="Font Shortcut key", + description="KEY label color", + subtype='COLOR_GAMMA', + default=(0.67, 0.67, 0.67, 1.0), + size=4, + min=0, max=1 + ) + feedback_colour_shortcut = FloatVectorProperty( + name="Font Shortcut hint", + description="Shortcuts text color", + subtype='COLOR_GAMMA', + default=(0.51, 0.51, 0.51, 1.0), + size=4, + min=0, max=1 + ) + + def draw(self, context): + layout = self.layout + box = layout.box() + row = box.row() + col = row.column() + col.label(text="Tab Category:") + col.prop(self, "tools_category") + col.prop(self, "create_category") + col.prop(self, "create_submenu") + col.prop(self, "max_style_draw_tool") + box = layout.box() + row = box.row() + split = row.split(percentage=0.5) + col = split.column() + col.label(text="Colors:") + row = col.row(align=True) + row.prop(self, "feedback_title_area") + row = col.row(align=True) + row.prop(self, "feedback_shortcut_area") + row = col.row(align=True) + row.prop(self, "feedback_colour_main") + row = col.row(align=True) + row.prop(self, "feedback_colour_key") + row = col.row(align=True) + row.prop(self, "feedback_colour_shortcut") + col = split.column() + col.label(text="Font size:") + col.prop(self, "feedback_size_main") + col.prop(self, "feedback_size_title") + col.prop(self, "feedback_size_shortcut") + col.label(text="Manipulators:") + col.prop(self, "arrow_size") + col.prop(self, "handle_size") + + +# ---------------------------------------------------- +# Archipack panels +# ---------------------------------------------------- + + +class TOOLS_PT_Archipack_PolyLib(Panel): + bl_label = "Archipack 2d to 3d" + bl_idname = "TOOLS_PT_Archipack_PolyLib" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_category = "Tools" + bl_context = "objectmode" + + @classmethod + def poll(self, context): + + global archipack_polylib + return HAS_POLYLIB and ((archipack_polylib.vars_dict['select_polygons'] is not None) or + (context.object is not None and context.object.type == 'CURVE')) + + def draw(self, context): + global icons_collection + icons = icons_collection["main"] + layout = self.layout + row = layout.row(align=True) + box = row.box() + row = box.row(align=True) + row.operator( + "archipack.polylib_detect", + icon_value=icons["detect"].icon_id, + text='Detect' + ).extend = context.window_manager.archipack_polylib.extend + row.prop(context.window_manager.archipack_polylib, "extend") + row = box.row(align=True) + row.prop(context.window_manager.archipack_polylib, "resolution") + row = box.row(align=True) + row.label(text="Polygons") + row = box.row(align=True) + row.operator( + "archipack.polylib_pick_2d_polygons", + icon_value=icons["selection"].icon_id, + text='Select' + ).action = 'select' + row.operator( + "archipack.polylib_pick_2d_polygons", + icon_value=icons["union"].icon_id, + text='Union' + ).action = 'union' + row.operator( + "archipack.polylib_output_polygons", + icon_value=icons["polygons"].icon_id, + text='All') + row = box.row(align=True) + row.operator( + "archipack.polylib_pick_2d_polygons", + text='Wall', + icon_value=icons["wall"].icon_id).action = 'wall' + row.prop(context.window_manager.archipack_polylib, "solidify_thickness") + row = box.row(align=True) + row.operator("archipack.polylib_pick_2d_polygons", + text='Window', + icon_value=icons["window"].icon_id).action = 'window' + row.operator("archipack.polylib_pick_2d_polygons", + text='Door', + icon_value=icons["door"].icon_id).action = 'door' + row.operator("archipack.polylib_pick_2d_polygons", text='Rectangle').action = 'rectangle' + row = box.row(align=True) + row.label(text="Lines") + row = box.row(align=True) + row.operator( + "archipack.polylib_pick_2d_lines", + icon_value=icons["selection"].icon_id, + text='Lines').action = 'select' + row.operator( + "archipack.polylib_pick_2d_lines", + icon_value=icons["union"].icon_id, + text='Union').action = 'union' + row.operator( + "archipack.polylib_output_lines", + icon_value=icons["polygons"].icon_id, + text='All') + row = box.row(align=True) + row.label(text="Points") + row = box.row(align=True) + row.operator( + "archipack.polylib_pick_2d_points", + icon_value=icons["selection"].icon_id, + text='Points').action = 'select' + row = layout.row(align=True) + box = row.box() + row = box.row(align=True) + row.operator("archipack.polylib_simplify") + row.prop(context.window_manager.archipack_polylib, "simplify_tolerance") + row = box.row(align=True) + row.prop(context.window_manager.archipack_polylib, "simplify_preserve_topology") + row = layout.row(align=True) + box = row.box() + row = box.row(align=True) + row.operator("archipack.polylib_offset") + row = box.row(align=True) + row.prop(context.window_manager.archipack_polylib, "offset_distance") + row = box.row(align=True) + row.prop(context.window_manager.archipack_polylib, "offset_side") + row = box.row(align=True) + row.prop(context.window_manager.archipack_polylib, "offset_resolution") + row = box.row(align=True) + row.prop(context.window_manager.archipack_polylib, "offset_join_style") + row = box.row(align=True) + row.prop(context.window_manager.archipack_polylib, "offset_mitre_limit") + + +class TOOLS_PT_Archipack_Tools(Panel): + bl_label = "Archipack Tools" + bl_idname = "TOOLS_PT_Archipack_Tools" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_category = "Tools" + bl_context = "objectmode" + + @classmethod + def poll(self, context): + return True + + def draw(self, context): + wm = context.window_manager + layout = self.layout + row = layout.row(align=True) + box = row.box() + box.label("Auto boolean") + row = box.row(align=True) + row.operator("archipack.auto_boolean", text="AutoBoolean", icon='AUTO').mode = 'HYBRID' + row = layout.row(align=True) + box = row.box() + box.label("Rendering") + row = box.row(align=True) + row.prop(wm.archipack, 'render_type', text="") + row = box.row(align=True) + row.operator("archipack.render", icon='RENDER_STILL') + + +class TOOLS_PT_Archipack_Create(Panel): + bl_label = "Add Archipack" + bl_idname = "TOOLS_PT_Archipack_Create" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_category = "Create" + bl_context = "objectmode" + + @classmethod + def poll(self, context): + return True + + def draw(self, context): + global icons_collection + icons = icons_collection["main"] + layout = self.layout + row = layout.row(align=True) + box = row.box() + box.label("Objects") + row = box.row(align=True) + row.operator("archipack.window_preset_menu", + text="Window", + icon_value=icons["window"].icon_id + ).preset_operator = "archipack.window" + row.operator("archipack.window_preset_menu", + text="", + icon='GREASEPENCIL' + ).preset_operator = "archipack.window_draw" + row = box.row(align=True) + row.operator("archipack.door_preset_menu", + text="Door", + icon_value=icons["door"].icon_id + ).preset_operator = "archipack.door" + row.operator("archipack.door_preset_menu", + text="", + icon='GREASEPENCIL' + ).preset_operator = "archipack.door_draw" + row = box.row(align=True) + row.operator("archipack.stair_preset_menu", + text="Stair", + icon_value=icons["stair"].icon_id + ).preset_operator = "archipack.stair" + row = box.row(align=True) + row.operator("archipack.wall2", + icon_value=icons["wall"].icon_id + ) + row.operator("archipack.wall2_draw", text="Draw", icon='GREASEPENCIL') + row.operator("archipack.wall2_from_curve", text="", icon='CURVE_DATA') + + row = box.row(align=True) + row.operator("archipack.fence_preset_menu", + text="Fence", + icon_value=icons["fence"].icon_id + ).preset_operator = "archipack.fence" + row.operator("archipack.fence_from_curve", text="", icon='CURVE_DATA') + row = box.row(align=True) + row.operator("archipack.truss", + icon_value=icons["truss"].icon_id + ) + row = box.row(align=True) + row.operator("archipack.slab_from_curve", + icon_value=icons["slab"].icon_id + ) + row = box.row(align=True) + row.operator("archipack.wall2_from_slab", + icon_value=icons["wall"].icon_id) + row.operator("archipack.slab_from_wall", + icon_value=icons["slab"].icon_id + ).ceiling = False + row.operator("archipack.slab_from_wall", + text="->Ceiling", + icon_value=icons["slab"].icon_id + ).ceiling = True + row = box.row(align=True) + row.operator("archipack.floor_preset_menu", + text="Floor", + icon_value=icons["floor"].icon_id + ).preset_operator = "archipack.floor" + + +# ---------------------------------------------------- +# ALT + A menu +# ---------------------------------------------------- + + +def draw_menu(self, context): + global icons_collection + icons = icons_collection["main"] + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + + layout.operator("archipack.wall2", + text="Wall", + icon_value=icons["wall"].icon_id + ) + layout.operator("archipack.window_preset_menu", + text="Window", + icon_value=icons["window"].icon_id + ).preset_operator = "archipack.window" + layout.operator("archipack.door_preset_menu", + text="Door", + icon_value=icons["door"].icon_id + ).preset_operator = "archipack.door" + layout.operator("archipack.stair_preset_menu", + text="Stair", + icon_value=icons["stair"].icon_id + ).preset_operator = "archipack.stair" + layout.operator("archipack.fence_preset_menu", + text="Fence", + icon_value=icons["fence"].icon_id + ).preset_operator = "archipack.fence" + layout.operator("archipack.truss", + text="Truss", + icon_value=icons["truss"].icon_id + ) + layout.operator("archipack.floor_preset_menu", + text="Floor", + icon_value=icons["floor"].icon_id + ) + + +class ARCHIPACK_create_menu(Menu): + bl_label = 'Archipack' + bl_idname = 'ARCHIPACK_create_menu' + bl_context = "objectmode" + + def draw(self, context): + draw_menu(self, context) + + +def menu_func(self, context): + layout = self.layout + layout.separator() + global icons_collection + icons = icons_collection["main"] + + # either draw sub menu or right at end of this one + if context.user_preferences.addons[__name__].preferences.create_submenu: + layout.operator_context = 'INVOKE_REGION_WIN' + layout.menu("ARCHIPACK_create_menu", icon_value=icons["archipack"].icon_id) + else: + draw_menu(self, context) + + +# ---------------------------------------------------- +# Datablock to store global addon variables +# ---------------------------------------------------- + + +class archipack_data(PropertyGroup): + render_type = EnumProperty( + items=( + ('1', "Draw over", "Draw over last rendered image"), + ('2', "OpenGL", ""), + ('3', "Animation OpenGL", ""), + ('4', "Image", "Render image and draw over"), + ('5', "Animation", "Draw on each frame") + ), + name="Render type", + description="Render method" + ) + + +def register(): + global icons_collection + icons = previews.new() + icons_dir = os.path.join(os.path.dirname(__file__), "icons") + for icon in os.listdir(icons_dir): + name, ext = os.path.splitext(icon) + icons.load(name, os.path.join(icons_dir, icon), 'IMAGE') + icons_collection["main"] = icons + + archipack_snap.register() + archipack_manipulator.register() + archipack_reference_point.register() + archipack_autoboolean.register() + archipack_door.register() + archipack_window.register() + archipack_stair.register() + archipack_wall.register() + archipack_wall2.register() + archipack_slab.register() + archipack_fence.register() + archipack_truss.register() + archipack_floor.register() + archipack_rendering.register() + + if HAS_POLYLIB: + archipack_polylib.register() + + bpy.utils.register_class(archipack_data) + WindowManager.archipack = PointerProperty(type=archipack_data) + bpy.utils.register_class(Archipack_Pref) + update_panel(None, bpy.context) + bpy.utils.register_class(ARCHIPACK_create_menu) + bpy.types.INFO_MT_mesh_add.append(menu_func) + + +def unregister(): + global icons_collection + bpy.types.INFO_MT_mesh_add.remove(menu_func) + bpy.utils.unregister_class(ARCHIPACK_create_menu) + + bpy.utils.unregister_class(TOOLS_PT_Archipack_PolyLib) + bpy.utils.unregister_class(TOOLS_PT_Archipack_Tools) + bpy.utils.unregister_class(TOOLS_PT_Archipack_Create) + bpy.utils.unregister_class(Archipack_Pref) + + # unregister subs + archipack_snap.unregister() + archipack_manipulator.unregister() + archipack_reference_point.unregister() + archipack_autoboolean.unregister() + archipack_door.unregister() + archipack_window.unregister() + archipack_stair.unregister() + archipack_wall.unregister() + archipack_wall2.unregister() + archipack_slab.unregister() + archipack_fence.unregister() + archipack_truss.unregister() + archipack_floor.unregister() + archipack_rendering.unregister() + + if HAS_POLYLIB: + archipack_polylib.unregister() + + bpy.utils.unregister_class(archipack_data) + del WindowManager.archipack + + for icons in icons_collection.values(): + previews.remove(icons) + icons_collection.clear() + + +if __name__ == "__main__": + register() diff --git a/archipack/archipack_2d.py b/archipack/archipack_2d.py new file mode 100644 index 000000000..912e3cb85 --- /dev/null +++ b/archipack/archipack_2d.py @@ -0,0 +1,893 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +from mathutils import Vector, Matrix +from math import sin, cos, pi, atan2, sqrt, acos +import bpy +# allow to draw parts with gl for debug puropses +from .archipack_gl import GlBaseLine + + +class Projection(GlBaseLine): + + def __init__(self): + GlBaseLine.__init__(self) + + def proj_xy(self, t, next=None): + """ + length of projection of sections at crossing line / circle intersections + deformation unit vector for profil in xy axis + so f(x_profile) = position of point in xy plane + """ + if next is None: + return self.normal(t).v.normalized(), 1 + v0 = self.normal(1).v.normalized() + v1 = next.normal(0).v.normalized() + direction = v0 + v1 + adj = (v0 * self.length) * (v1 * next.length) + hyp = (self.length * next.length) + c = min(1, max(-1, adj / hyp)) + size = 1 / cos(0.5 * acos(c)) + return direction.normalized(), min(3, size) + + def proj_z(self, t, dz0, next=None, dz1=0): + """ + length of projection along crossing line / circle + deformation unit vector for profil in z axis at line / line intersection + so f(y) = position of point in yz plane + """ + return Vector((0, 1)), 1 + """ + NOTE (to myself): + In theory this is how it has to be done so sections follow path, + but in real world results are better when sections are z-up. + So return a dumb 1 so f(y) = y + """ + if next is None: + dz = dz0 / self.length + else: + dz = (dz1 + dz0) / (self.length + next.length) + return Vector((0, 1)), sqrt(1 + dz * dz) + # 1 / sqrt(1 + (dz0 / self.length) * (dz0 / self.length)) + if next is None: + return Vector((-dz0, self.length)).normalized(), 1 + v0 = Vector((self.length, dz0)) + v1 = Vector((next.length, dz1)) + direction = Vector((-dz0, self.length)).normalized() + Vector((-dz1, next.length)).normalized() + adj = v0 * v1 + hyp = (v0.length * v1.length) + c = min(1, max(-1, adj / hyp)) + size = -cos(pi - 0.5 * acos(c)) + return direction.normalized(), size + + +class Line(Projection): + """ + 2d Line + Internally stored as p: origin and v:size and direction + moving p will move both ends of line + moving p0 or p1 move only one end of line + p1 + ^ + | v + p0 == p + """ + def __init__(self, p=None, v=None, p0=None, p1=None): + """ + Init by either + p: Vector or tuple origin + v: Vector or tuple size and direction + or + p0: Vector or tuple 1 point location + p1: Vector or tuple 2 point location + Will convert any into Vector 2d + both optionnals + """ + Projection.__init__(self) + if p is not None and v is not None: + self.p = Vector(p).to_2d() + self.v = Vector(v).to_2d() + elif p0 is not None and p1 is not None: + self.p = Vector(p0).to_2d() + self.v = Vector(p1).to_2d() - self.p + else: + self.p = Vector((0, 0)) + self.v = Vector((0, 0)) + + @property + def p0(self): + return self.p + + @property + def p1(self): + return self.p + self.v + + @p0.setter + def p0(self, p0): + """ + Note: setting p0 + move p0 only + """ + p1 = self.p1 + self.p = Vector(p0).to_2d() + self.v = p1 - p0 + + @p1.setter + def p1(self, p1): + """ + Note: setting p1 + move p1 only + """ + self.v = Vector(p1).to_2d() - self.p + + @property + def length(self): + """ + 3d length + """ + return self.v.length + + @property + def angle(self): + """ + 2d angle on xy plane + """ + return atan2(self.v.y, self.v.x) + + @property + def angle_normal(self): + """ + 2d angle of perpendicular + lie on the right side + p1 + |--x + p0 + """ + return atan2(-self.v.x, self.v.y) + + @property + def reversed(self): + return Line(self.p, -self.v) + + @property + def oposite(self): + return Line(self.p + self.v, -self.v) + + @property + def cross_z(self): + """ + 2d Vector perpendicular on plane xy + lie on the right side + p1 + |--x + p0 + """ + return Vector((self.v.y, -self.v.x)) + + @property + def cross(self): + return Vector((self.v.y, -self.v.x)) + + def signed_angle(self, u, v): + """ + signed angle between two vectors range [-pi, pi] + """ + return atan2(u.x * v.y - u.y * v.x, u.x * v.x + u.y * v.y) + + def delta_angle(self, last): + """ + signed delta angle between end of line and start of this one + this value is object's a0 for segment = self + """ + if last is None: + return self.angle + return self.signed_angle(last.straight(1, 1).v, self.straight(1, 0).v) + + def normal(self, t=0): + """ + 2d Line perpendicular on plane xy + at position t in current segment + lie on the right side + p1 + |--x + p0 + """ + return Line(self.lerp(t), self.cross_z) + + def sized_normal(self, t, size): + """ + 2d Line perpendicular on plane xy + at position t in current segment + and of given length + lie on the right side when size > 0 + p1 + |--x + p0 + """ + return Line(self.lerp(t), size * self.cross_z.normalized()) + + def lerp(self, t): + """ + 3d interpolation + """ + return self.p + self.v * t + + def intersect(self, line): + """ + 2d intersection on plane xy + return + True if intersect + p: point of intersection + t: param t of intersection on current line + """ + c = line.cross_z + d = self.v * c + if d == 0: + return False, 0, 0 + t = (c * (line.p - self.p)) / d + return True, self.lerp(t), t + + def point_sur_segment(self, pt): + """ _point_sur_segment + point: Vector 2d + t: param t de l'intersection sur le segment courant + d: distance laterale perpendiculaire positif a droite + """ + dp = pt - self.p + dl = self.length + d = (self.v.x * dp.y - self.v.y * dp.x) / dl + t = (self.v * dp) / (dl * dl) + return t > 0 and t < 1, d, t + + def steps(self, len): + steps = max(1, round(self.length / len, 0)) + return 1 / steps, int(steps) + + def in_place_offset(self, offset): + """ + Offset current line + offset > 0 on the right part + """ + self.p += offset * self.cross_z.normalized() + + def offset(self, offset): + """ + Return a new line + offset > 0 on the right part + """ + return Line(self.p + offset * self.cross_z.normalized(), self.v) + + def tangeant(self, t, da, radius): + p = self.lerp(t) + if da < 0: + c = p + radius * self.cross_z.normalized() + else: + c = p - radius * self.cross_z.normalized() + return Arc(c, radius, self.angle_normal, da) + + def straight(self, length, t=1): + return Line(self.lerp(t), self.v.normalized() * length) + + def translate(self, dp): + self.p += dp + + def rotate(self, a): + """ + Rotate segment ccw arroud p0 + """ + ca = cos(a) + sa = sin(a) + self.v = Matrix([ + [ca, -sa], + [sa, ca] + ]) * self.v + return self + + def scale(self, length): + self.v = length * self.v.normalized() + return self + + def tangeant_unit_vector(self, t): + return self.v.normalized() + + def as_curve(self, context): + """ + Draw Line with open gl in screen space + aka: coords are in pixels + """ + raise NotImplementedError + + def make_offset(self, offset, last=None): + """ + Return offset between last and self. + Adjust last and self start to match + intersection point + """ + line = self.offset(offset) + if last is None: + return line + + if hasattr(last, "r"): + res, d, t = line.point_sur_segment(last.c) + c = (last.r * last.r) - (d * d) + print("t:%s" % t) + if c <= 0: + # no intersection ! + p0 = line.lerp(t) + else: + # center is past start of line + if t > 0: + p0 = line.lerp(t) - line.v.normalized() * sqrt(c) + else: + p0 = line.lerp(t) + line.v.normalized() * sqrt(c) + # compute da of arc + u = last.p0 - last.c + v = p0 - last.c + da = self.signed_angle(u, v) + # da is ccw + if last.ccw: + # da is cw + if da < 0: + # so take inverse + da = 2 * pi + da + elif da > 0: + # da is ccw + da = 2 * pi - da + last.da = da + line.p0 = p0 + else: + # intersect line / line + # 1 line -> 2 line + c = line.cross_z + d = last.v * c + if d == 0: + return line + v = line.p - last.p + t = (c * v) / d + c2 = last.cross_z + u = (c2 * v) / d + # intersect past this segment end + # or before last segment start + # print("u:%s t:%s" % (u, t)) + if u > 1 or t < 0: + return line + p = last.lerp(t) + line.p0 = p + last.p1 = p + + return line + + @property + def pts(self): + return [self.p0.to_3d(), self.p1.to_3d()] + + +class Circle(Projection): + def __init__(self, c, radius): + Projection.__init__(self) + self.r = radius + self.r2 = radius * radius + self.c = c + + def intersect(self, line): + v = line.p - self.c + A = line.v * line.v + B = 2 * v * line.v + C = v * v - self.r2 + d = B * B - 4 * A * C + if A <= 0.0000001 or d < 0: + # dosent intersect, find closest point of line + res, d, t = line.point_sur_segment(self.c) + return False, line.lerp(t), t + elif d == 0: + t = -B / 2 * A + return True, line.lerp(t), t + else: + AA = 2 * A + dsq = sqrt(d) + t0 = (-B + dsq) / AA + t1 = (-B - dsq) / AA + if abs(t0) < abs(t1): + return True, line.lerp(t0), t0 + else: + return True, line.lerp(t1), t1 + + def translate(self, dp): + self.c += dp + + +class Arc(Circle): + """ + Represent a 2d Arc + TODO: + Add some sugar here + like being able to set p0 and p1 of line + make it possible to define an arc by start point end point and center + """ + def __init__(self, c, radius, a0, da): + """ + a0 and da arguments are in radians + c Vector 2d center + radius float radius + a0 radians start angle + da radians delta angle from start to end + a0 = 0 on the right side + a0 = pi on the left side + da > 0 CCW contrary-clockwise + da < 0 CW clockwise + stored internally as radians + """ + Circle.__init__(self, Vector(c).to_2d(), radius) + self.a0 = a0 + self.da = da + + @property + def angle(self): + """ + angle of vector p0 p1 + """ + v = self.p1 - self.p0 + return atan2(v.y, v.x) + + @property + def ccw(self): + return self.da > 0 + + def signed_angle(self, u, v): + """ + signed angle between two vectors + """ + return atan2(u.x * v.y - u.y * v.x, u.x * v.x + u.y * v.y) + + def delta_angle(self, last): + """ + signed delta angle between end of line and start of this one + this value is object's a0 for segment = self + """ + if last is None: + return self.a0 + return self.signed_angle(last.straight(1, 1).v, self.straight(1, 0).v) + + def scale_rot_matrix(self, u, v): + """ + given vector u and v (from and to p0 p1) + apply scale factor to radius and + return a matrix to rotate and scale + the center around u origin so + arc fit v + """ + # signed angle old new vectors (rotation) + a = self.signed_angle(u, v) + # scale factor + scale = v.length / u.length + ca = scale * cos(a) + sa = scale * sin(a) + return scale, Matrix([ + [ca, -sa], + [sa, ca] + ]) + + @property + def p0(self): + """ + start point of arc + """ + return self.lerp(0) + + @property + def p1(self): + """ + end point of arc + """ + return self.lerp(1) + + @p0.setter + def p0(self, p0): + """ + rotate and scale arc so it intersect p0 p1 + da is not affected + """ + u = self.p0 - self.p1 + v = p0 - self.p1 + scale, rM = self.scale_rot_matrix(u, v) + self.c = self.p1 + rM * (self.c - self.p1) + self.r *= scale + self.r2 = self.r * self.r + dp = p0 - self.c + self.a0 = atan2(dp.y, dp.x) + + @p1.setter + def p1(self, p1): + """ + rotate and scale arc so it intersect p0 p1 + da is not affected + """ + p0 = self.p0 + u = self.p1 - p0 + v = p1 - p0 + + scale, rM = self.scale_rot_matrix(u, v) + self.c = p0 + rM * (self.c - p0) + self.r *= scale + self.r2 = self.r * self.r + dp = p0 - self.c + self.a0 = atan2(dp.y, dp.x) + + @property + def length(self): + """ + arc length + """ + return self.r * abs(self.da) + + def normal(self, t=0): + """ + Perpendicular line starting at t + always on the right side + """ + p = self.lerp(t) + if self.da < 0: + return Line(p, self.c - p) + else: + return Line(p, p - self.c) + + def sized_normal(self, t, size): + """ + Perpendicular line starting at t and of a length size + on the right side when size > 0 + """ + p = self.lerp(t) + if self.da < 0: + v = self.c - p + else: + v = p - self.c + return Line(p, size * v.normalized()) + + def lerp(self, t): + """ + Interpolate along segment + t parameter [0, 1] where 0 is start of arc and 1 is end + """ + a = self.a0 + t * self.da + return self.c + Vector((self.r * cos(a), self.r * sin(a))) + + def steps(self, length): + """ + Compute step count given desired step length + """ + steps = max(1, round(self.length / length, 0)) + return 1.0 / steps, int(steps) + + # this is for wall + def steps_by_angle(self, step_angle): + steps = max(1, round(abs(self.da) / step_angle, 0)) + return 1.0 / steps, int(steps) + + def offset(self, offset): + """ + Offset circle + offset > 0 on the right part + """ + if self.da > 0: + radius = self.r + offset + else: + radius = self.r - offset + return Arc(self.c, radius, self.a0, self.da) + + def tangeant(self, t, length): + """ + Tangeant line so we are able to chain Circle and lines + Beware, counterpart on Line does return an Arc ! + """ + a = self.a0 + t * self.da + ca = cos(a) + sa = sin(a) + p = self.c + Vector((self.r * ca, self.r * sa)) + v = Vector((length * sa, -length * ca)) + if self.da > 0: + v = -v + return Line(p, v) + + def tangeant_unit_vector(self, t): + """ + Return Tangeant vector of length 1 + """ + a = self.a0 + t * self.da + ca = cos(a) + sa = sin(a) + v = Vector((sa, -ca)) + if self.da > 0: + v = -v + return v + + def straight(self, length, t=1): + """ + Return a tangeant Line + Counterpart on Line also return a Line + """ + return self.tangeant(t, length) + + def point_sur_segment(self, pt): + """ + Point pt lie on arc ? + return + True when pt lie on segment + t [0, 1] where it lie (normalized between start and end) + d distance from arc + """ + dp = pt - self.c + d = dp.length - self.r + a = atan2(dp.y, dp.x) + t = (a - self.a0) / self.da + return t > 0 and t < 1, d, t + + def rotate(self, a): + """ + Rotate center so we rotate ccw arround p0 + """ + ca = cos(a) + sa = sin(a) + rM = Matrix([ + [ca, -sa], + [sa, ca] + ]) + p0 = self.p0 + self.c = p0 + rM * (self.c - p0) + dp = p0 - self.c + self.a0 = atan2(dp.y, dp.x) + return self + + # make offset for line / arc, arc / arc + def make_offset(self, offset, last=None): + + line = self.offset(offset) + + if last is None: + return line + + if hasattr(last, "v"): + # intersect line / arc + # 1 line -> 2 arc + res, d, t = last.point_sur_segment(line.c) + c = line.r2 - (d * d) + if c <= 0: + # no intersection ! + p0 = last.lerp(t) + else: + + # center is past end of line + if t > 1: + # Arc take precedence + p0 = last.lerp(t) - last.v.normalized() * sqrt(c) + else: + # line take precedence + p0 = last.lerp(t) + last.v.normalized() * sqrt(c) + + # compute a0 and da of arc + u = p0 - line.c + v = line.p1 - line.c + line.a0 = atan2(u.y, u.x) + da = self.signed_angle(u, v) + # da is ccw + if self.ccw: + # da is cw + if da < 0: + # so take inverse + da = 2 * pi + da + elif da > 0: + # da is ccw + da = 2 * pi - da + line.da = da + last.p1 = p0 + else: + # intersect arc / arc x1 = self x0 = last + # rule to determine right side -> + # same side of d as p0 of self + dc = line.c - last.c + tmp = Line(last.c, dc) + res, d, t = tmp.point_sur_segment(self.p0) + r = line.r + last.r + dist = dc.length + if dist > r or \ + dist < abs(last.r - self.r): + # no intersection + return line + if dist == r: + # 1 solution + p0 = dc * -last.r / r + self.c + else: + # 2 solutions + a = (last.r2 - line.r2 + dist * dist) / (2.0 * dist) + v2 = last.c + dc * a / dist + h = sqrt(last.r2 - a * a) + r = Vector((-dc.y, dc.x)) * (h / dist) + p0 = v2 + r + res, d1, t = tmp.point_sur_segment(p0) + # take other point if we are not on the same side + if d1 > 0: + if d < 0: + p0 = v2 - r + elif d > 0: + p0 = v2 - r + + # compute da of last + u = last.p0 - last.c + v = p0 - last.c + last.da = self.signed_angle(u, v) + + # compute a0 and da of current + u, v = v, line.p1 - line.c + line.a0 = atan2(u.y, u.x) + line.da = self.signed_angle(u, v) + return line + + # DEBUG + @property + def pts(self): + n_pts = max(1, int(round(abs(self.da) / pi * 30, 0))) + t_step = 1 / n_pts + return [self.lerp(i * t_step).to_3d() for i in range(n_pts + 1)] + + def as_curve(self, context): + """ + Draw 2d arc with open gl in screen space + aka: coords are in pixels + """ + curve = bpy.data.curves.new('ARC', type='CURVE') + curve.dimensions = '2D' + spline = curve.splines.new('POLY') + spline.use_endpoint_u = False + spline.use_cyclic_u = False + pts = self.pts + spline.points.add(len(pts) - 1) + for i, p in enumerate(pts): + x, y = p + spline.points[i].co = (x, y, 0, 1) + curve_obj = bpy.data.objects.new('ARC', curve) + context.scene.objects.link(curve_obj) + curve_obj.select = True + + +class Line3d(Line): + """ + 3d Line + mostly a gl enabled for future use in manipulators + coords are in world space + """ + def __init__(self, p=None, v=None, p0=None, p1=None, z_axis=None): + """ + Init by either + p: Vector or tuple origin + v: Vector or tuple size and direction + or + p0: Vector or tuple 1 point location + p1: Vector or tuple 2 point location + Will convert any into Vector 3d + both optionnals + """ + if p is not None and v is not None: + self.p = Vector(p).to_3d() + self.v = Vector(v).to_3d() + elif p0 is not None and p1 is not None: + self.p = Vector(p0).to_3d() + self.v = Vector(p1).to_3d() - self.p + else: + self.p = Vector((0, 0, 0)) + self.v = Vector((0, 0, 0)) + if z_axis is not None: + self.z_axis = z_axis + else: + self.z_axis = Vector((0, 0, 1)) + + @property + def p0(self): + return self.p + + @property + def p1(self): + return self.p + self.v + + @p0.setter + def p0(self, p0): + """ + Note: setting p0 + move p0 only + """ + p1 = self.p1 + self.p = Vector(p0).to_3d() + self.v = p1 - p0 + + @p1.setter + def p1(self, p1): + """ + Note: setting p1 + move p1 only + """ + self.v = Vector(p1).to_3d() - self.p + + @property + def cross_z(self): + """ + 3d Vector perpendicular on plane xy + lie on the right side + p1 + |--x + p0 + """ + return self.v.cross(Vector((0, 0, 1))) + + @property + def cross(self): + """ + 3d Vector perpendicular on plane defined by z_axis + lie on the right side + p1 + |--x + p0 + """ + return self.v.cross(self.z_axis) + + def normal(self, t=0): + """ + 3d Vector perpendicular on plane defined by z_axis + lie on the right side + p1 + |--x + p0 + """ + n = Line3d() + n.p = self.lerp(t) + n.v = self.cross + return n + + def sized_normal(self, t, size): + """ + 3d Line perpendicular on plane defined by z_axis and of given size + positionned at t in current line + lie on the right side + p1 + |--x + p0 + """ + p = self.lerp(t) + v = size * self.cross.normalized() + return Line3d(p, v, z_axis=self.z_axis) + + def offset(self, offset): + """ + offset > 0 on the right part + """ + return Line3d(self.p + offset * self.cross.normalized(), self.v) + + # unless override, 2d methods should raise NotImplementedError + def intersect(self, line): + raise NotImplementedError + + def point_sur_segment(self, pt): + raise NotImplementedError + + def tangeant(self, t, da, radius): + raise NotImplementedError diff --git a/archipack/archipack_autoboolean.py b/archipack/archipack_autoboolean.py new file mode 100644 index 000000000..a171532cf --- /dev/null +++ b/archipack/archipack_autoboolean.py @@ -0,0 +1,678 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +import bpy +from bpy.types import Operator +from bpy.props import EnumProperty +from mathutils import Vector +from .materialutils import MaterialUtils + +from os import path + + +def debug_using_gl(context, filename): + context.scene.update() + temp_path = "C:\\tmp\\" + context.scene.render.filepath = path.join(temp_path, filename + ".png") + bpy.ops.render.opengl(write_still=True) + + +class ArchipackBoolManager(): + """ + Handle three methods for booleans + - interactive: one modifier for each hole right on wall + - robust: one single modifier on wall and merge holes in one mesh + - mixed: merge holes with boolean and use result on wall + may be slow, but is robust + """ + def __init__(self, mode, solver_mode='CARVE'): + """ + mode in 'ROBUST', 'INTERACTIVE', 'HYBRID' + """ + self.mode = mode + self.solver_mode = solver_mode + # internal variables + self.itM = None + self.min_x = 0 + self.min_y = 0 + self.min_z = 0 + self.max_x = 0 + self.max_y = 0 + self.max_z = 0 + + def _get_bounding_box(self, wall): + self.itM = wall.matrix_world.inverted() + x, y, z = wall.bound_box[0] + self.min_x = x + self.min_y = y + self.min_z = z + x, y, z = wall.bound_box[6] + self.max_x = x + self.max_y = y + self.max_z = z + self.center = Vector(( + self.min_x + 0.5 * (self.max_x - self.min_x), + self.min_y + 0.5 * (self.max_y - self.min_y), + self.min_z + 0.5 * (self.max_z - self.min_z))) + + def _contains(self, pt): + p = self.itM * pt + return (p.x >= self.min_x and p.x <= self.max_x and + p.y >= self.min_y and p.y <= self.max_y and + p.z >= self.min_z and p.z <= self.max_z) + + def filter_wall(self, wall): + d = wall.data + return (d is None or + 'archipack_window' in d or + 'archipack_window_panel' in d or + 'archipack_door' in d or + 'archipack_doorpanel' in d or + 'archipack_hole' in wall or + 'archipack_robusthole' in wall or + 'archipack_handle' in wall) + + def datablock(self, o): + """ + get datablock from windows and doors + return + datablock if found + None when not found + """ + d = None + if o.data: + if "archipack_window" in o.data: + d = o.data.archipack_window[0] + elif "archipack_door" in o.data: + d = o.data.archipack_door[0] + return d + + def prepare_hole(self, hole): + hole.lock_location = (True, True, True) + hole.lock_rotation = (True, True, True) + hole.lock_scale = (True, True, True) + hole.draw_type = 'WIRE' + hole.hide_render = True + hole.hide_select = True + hole.select = True + hole.cycles_visibility.camera = False + hole.cycles_visibility.diffuse = False + hole.cycles_visibility.glossy = False + hole.cycles_visibility.shadow = False + hole.cycles_visibility.scatter = False + hole.cycles_visibility.transmission = False + + def get_child_hole(self, o): + for hole in o.children: + if "archipack_hole" in hole: + return hole + return None + + def _generate_hole(self, context, o): + # use existing one + if self.mode != 'ROBUST': + hole = self.get_child_hole(o) + if hole is not None: + # print("_generate_hole Use existing hole %s" % (hole.name)) + return hole + # generate single hole from archipack primitives + d = self.datablock(o) + hole = None + if d is not None: + if (self.itM is not None and ( + self._contains(o.location) or + self._contains(o.matrix_world * Vector((0, 0, 0.5 * d.z)))) + ): + if self.mode != 'ROBUST': + hole = d.interactive_hole(context, o) + else: + hole = d.robust_hole(context, o.matrix_world) + # print("_generate_hole Generate hole %s" % (hole.name)) + else: + hole = d.interactive_hole(context, o) + return hole + + def partition(self, array, begin, end): + pivot = begin + for i in range(begin + 1, end + 1): + if array[i][1] <= array[begin][1]: + pivot += 1 + array[i], array[pivot] = array[pivot], array[i] + array[pivot], array[begin] = array[begin], array[pivot] + return pivot + + def quicksort(self, array, begin=0, end=None): + if end is None: + end = len(array) - 1 + + def _quicksort(array, begin, end): + if begin >= end: + return + pivot = self.partition(array, begin, end) + _quicksort(array, begin, pivot - 1) + _quicksort(array, pivot + 1, end) + return _quicksort(array, begin, end) + + def sort_holes(self, wall, holes): + """ + sort hole from center to borders by distance from center + may improve nested booleans + """ + center = wall.matrix_world * self.center + holes = [(o, (o.matrix_world.translation - center).length) for o in holes] + self.quicksort(holes) + return [o[0] for o in holes] + + def difference(self, basis, hole, solver=None): + # print("difference %s" % (hole.name)) + m = basis.modifiers.new('AutoBoolean', 'BOOLEAN') + m.operation = 'DIFFERENCE' + if solver is None: + m.solver = self.solver_mode + else: + m.solver = solver + m.object = hole + + def union(self, basis, hole): + # print("union %s" % (hole.name)) + m = basis.modifiers.new('AutoMerge', 'BOOLEAN') + m.operation = 'UNION' + m.solver = self.solver_mode + m.object = hole + + def remove_modif_and_object(self, context, o, to_delete): + # print("remove_modif_and_object removed:%s" % (len(to_delete))) + for m, h in to_delete: + if m is not None: + if m.object is not None: + m.object = None + o.modifiers.remove(m) + if h is not None: + context.scene.objects.unlink(h) + bpy.data.objects.remove(h, do_unlink=True) + + # Mixed + def create_merge_basis(self, context, wall): + # print("create_merge_basis") + h = bpy.data.meshes.new("AutoBoolean") + hole_obj = bpy.data.objects.new("AutoBoolean", h) + context.scene.objects.link(hole_obj) + hole_obj['archipack_hybridhole'] = True + if wall.parent is not None: + hole_obj.parent = wall.parent + hole_obj.matrix_world = wall.matrix_world.copy() + MaterialUtils.add_wall2_materials(hole_obj) + return hole_obj + + def update_hybrid(self, context, wall, childs, holes): + """ + Update all holes modifiers + remove holes not found in childs + + robust -> mixed: + there is only one object taged with "archipack_robusthole" + interactive -> mixed: + many modifisers on wall taged with "archipack_hole" + keep objects + """ + existing = [] + to_delete = [] + + # robust/interactive -> mixed + for m in wall.modifiers: + if m.type == 'BOOLEAN': + if m.object is None: + to_delete.append([m, None]) + elif 'archipack_hole' in m.object: + h = m.object + if h in holes: + to_delete.append([m, None]) + else: + to_delete.append([m, h]) + elif 'archipack_robusthole' in m.object: + to_delete.append([m, m.object]) + + # remove modifier and holes not found in new list + self.remove_modif_and_object(context, wall, to_delete) + + m = wall.modifiers.get("AutoMixedBoolean") + if m is None: + m = wall.modifiers.new('AutoMixedBoolean', 'BOOLEAN') + m.solver = self.solver_mode + m.operation = 'DIFFERENCE' + + if m.object is None: + hole_obj = self.create_merge_basis(context, wall) + else: + hole_obj = m.object + # debug_using_gl(context, "260") + m.object = hole_obj + self.prepare_hole(hole_obj) + # debug_using_gl(context, "263") + to_delete = [] + + # mixed-> mixed + for m in hole_obj.modifiers: + h = m.object + if h in holes: + existing.append(h) + else: + to_delete.append([m, h]) + + # remove modifier and holes not found in new list + self.remove_modif_and_object(context, hole_obj, to_delete) + # debug_using_gl(context, "276") + # add modifier and holes not found in existing + for h in holes: + if h not in existing: + self.union(hole_obj, h) + # debug_using_gl(context, "281") + + # Interactive + def update_interactive(self, context, wall, childs, holes): + + existing = [] + + to_delete = [] + + hole_obj = None + + # mixed-> interactive + for m in wall.modifiers: + if m.type == 'BOOLEAN': + if m.object is not None and 'archipack_hybridhole' in m.object: + hole_obj = m.object + break + + if hole_obj is not None: + for m in hole_obj.modifiers: + h = m.object + if h not in holes: + to_delete.append([m, h]) + # remove modifier and holes not found in new list + self.remove_modif_and_object(context, hole_obj, to_delete) + context.scene.objects.unlink(hole_obj) + bpy.data.objects.remove(hole_obj, do_unlink=True) + + to_delete = [] + + # interactive/robust -> interactive + for m in wall.modifiers: + if m.type == 'BOOLEAN': + if m.object is None: + to_delete.append([m, None]) + elif 'archipack_hole' in m.object: + h = m.object + if h in holes: + existing.append(h) + else: + to_delete.append([m, h]) + elif 'archipack_robusthole' in m.object: + to_delete.append([m, m.object]) + + # remove modifier and holes not found in new list + self.remove_modif_and_object(context, wall, to_delete) + + # add modifier and holes not found in existing + for h in holes: + if h not in existing: + self.difference(wall, h) + + # Robust + def update_robust(self, context, wall, childs): + + modif = None + + to_delete = [] + + # robust/interactive/mixed -> robust + for m in wall.modifiers: + if m.type == 'BOOLEAN': + if m.object is None: + to_delete.append([m, None]) + elif 'archipack_robusthole' in m.object: + modif = m + to_delete.append([None, m.object]) + elif 'archipack_hole' in m.object: + to_delete.append([m, m.object]) + elif 'archipack_hybridhole' in m.object: + to_delete.append([m, m.object]) + o = m.object + for m in o.modifiers: + to_delete.append([None, m.object]) + + # remove modifier and holes + self.remove_modif_and_object(context, wall, to_delete) + + if bool(len(context.selected_objects) > 0): + # more than one hole : join, result becomes context.object + if len(context.selected_objects) > 1: + bpy.ops.object.join() + context.object['archipack_robusthole'] = True + + hole = context.object + hole.name = 'AutoBoolean' + + childs.append(hole) + + if modif is None: + self.difference(wall, hole) + else: + modif.object = hole + elif modif is not None: + wall.modifiers.remove(modif) + + def autoboolean(self, context, wall): + """ + Entry point for multi-boolean operations like + in T panel autoBoolean and RobustBoolean buttons + """ + bpy.ops.object.select_all(action='DESELECT') + context.scene.objects.active = None + childs = [] + holes = [] + # get wall bounds to find what's inside + self._get_bounding_box(wall) + + # either generate hole or get existing one + for o in context.scene.objects: + h = self._generate_hole(context, o) + if h is not None: + holes.append(h) + childs.append(o) + # debug_using_gl(context, "395") + self.sort_holes(wall, holes) + + # hole(s) are selected and active after this one + for hole in holes: + self.prepare_hole(hole) + # debug_using_gl(context, "401") + + # update / remove / add boolean modifier + if self.mode == 'INTERACTIVE': + self.update_interactive(context, wall, childs, holes) + elif self.mode == 'ROBUST': + self.update_robust(context, wall, childs) + else: + self.update_hybrid(context, wall, childs, holes) + + bpy.ops.object.select_all(action='DESELECT') + # parenting childs to wall reference point + if wall.parent is None: + x, y, z = wall.bound_box[0] + context.scene.cursor_location = wall.matrix_world * Vector((x, y, z)) + # fix issue #9 + context.scene.objects.active = wall + bpy.ops.archipack.reference_point() + else: + wall.parent.select = True + context.scene.objects.active = wall.parent + # debug_using_gl(context, "422") + wall.select = True + for o in childs: + if 'archipack_robusthole' in o: + o.hide_select = False + o.select = True + # debug_using_gl(context, "428") + + bpy.ops.archipack.parent_to_reference() + + for o in childs: + if 'archipack_robusthole' in o: + o.hide_select = True + # debug_using_gl(context, "435") + + def detect_mode(self, context, wall): + for m in wall.modifiers: + if m.type == 'BOOLEAN' and m.object is not None: + if 'archipack_hole' in m.object: + self.mode = 'INTERACTIVE' + if 'archipack_hybridhole' in m.object: + self.mode = 'HYBRID' + if 'archipack_robusthole' in m.object: + self.mode = 'ROBUST' + + def singleboolean(self, context, wall, o): + """ + Entry point for single boolean operations + in use in draw door and windows over wall + o is either a window or a door + """ + # generate holes for crossing window and doors + self.itM = wall.matrix_world.inverted() + d = self.datablock(o) + hole = None + hole_obj = None + # default mode defined by __init__ + self.detect_mode(context, wall) + + if d is not None: + if self.mode != 'ROBUST': + hole = d.interactive_hole(context, o) + else: + hole = d.robust_hole(context, o.matrix_world) + if hole is None: + return + + self.prepare_hole(hole) + + if self.mode == 'INTERACTIVE': + # update / remove / add boolean modifier + self.difference(wall, hole) + + elif self.mode == 'HYBRID': + m = wall.modifiers.get('AutoMixedBoolean') + + if m is None: + m = wall.modifiers.new('AutoMixedBoolean', 'BOOLEAN') + m.operation = 'DIFFERENCE' + m.solver = self.solver_mode + + if m.object is None: + hole_obj = self.create_merge_basis(context, wall) + m.object = hole_obj + else: + hole_obj = m.object + self.union(hole_obj, hole) + + bpy.ops.object.select_all(action='DESELECT') + + # parenting childs to wall reference point + if wall.parent is None: + x, y, z = wall.bound_box[0] + context.scene.cursor_location = wall.matrix_world * Vector((x, y, z)) + # fix issue #9 + context.scene.objects.active = wall + bpy.ops.archipack.reference_point() + else: + context.scene.objects.active = wall.parent + + if hole_obj is not None: + hole_obj.select = True + + wall.select = True + o.select = True + bpy.ops.archipack.parent_to_reference() + wall.select = True + context.scene.objects.active = wall + d = wall.data.archipack_wall2[0] + g = d.get_generator() + d.setup_childs(wall, g) + d.relocate_childs(context, wall, g) + + if hole_obj is not None: + self.prepare_hole(hole_obj) + + +class ARCHIPACK_OT_single_boolean(Operator): + bl_idname = "archipack.single_boolean" + bl_label = "SingleBoolean" + bl_description = "Add single boolean for doors and windows" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + mode = EnumProperty( + name="Mode", + items=( + ('INTERACTIVE', 'INTERACTIVE', 'Interactive, fast but may fail', 0), + ('ROBUST', 'ROBUST', 'Not interactive, robust', 1), + ('HYBRID', 'HYBRID', 'Interactive, slow but robust', 2) + ), + default='HYBRID' + ) + solver_mode = EnumProperty( + name="Solver", + items=( + ('CARVE', 'CARVE', 'Slow but robust (could be slow in hybrid mode with many holes)', 0), + ('BMESH', 'BMESH', 'Fast but more prone to errors', 1) + ), + default='BMESH' + ) + """ + Wall must be active object + window or door must be selected + """ + + @classmethod + def poll(cls, context): + w = context.active_object + return (w.data is not None and + "archipack_wall2" in w.data and + len(context.selected_objects) == 2 + ) + + def draw(self, context): + pass + + def execute(self, context): + if context.mode == "OBJECT": + wall = context.active_object + manager = ArchipackBoolManager(mode=self.mode, solver_mode=self.solver_mode) + for o in context.selected_objects: + if o != wall: + manager.singleboolean(context, wall, o) + break + o.select = False + wall.select = True + context.scene.objects.active = wall + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_auto_boolean(Operator): + bl_idname = "archipack.auto_boolean" + bl_label = "AutoBoolean" + bl_description = "Automatic boolean for doors and windows" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + mode = EnumProperty( + name="Mode", + items=( + ('INTERACTIVE', 'INTERACTIVE', 'Interactive, fast but may fail', 0), + ('ROBUST', 'ROBUST', 'Not interactive, robust', 1), + ('HYBRID', 'HYBRID', 'Interactive, slow but robust', 2) + ), + default='HYBRID' + ) + solver_mode = EnumProperty( + name="Solver", + items=( + ('CARVE', 'CARVE', 'Slow but robust (could be slow in hybrid mode with many holes)', 0), + ('BMESH', 'BMESH', 'Fast but more prone to errors', 1) + ), + default='BMESH' + ) + + def draw(self, context): + layout = self.layout + row = layout.row() + row.prop(self, 'mode') + row.prop(self, 'solver_mode') + + def execute(self, context): + if context.mode == "OBJECT": + manager = ArchipackBoolManager(mode=self.mode, solver_mode=self.solver_mode) + active = context.scene.objects.active + walls = [wall for wall in context.selected_objects if not manager.filter_wall(wall)] + bpy.ops.object.select_all(action='DESELECT') + for wall in walls: + manager.autoboolean(context, wall) + bpy.ops.object.select_all(action='DESELECT') + wall.select = True + context.scene.objects.active = wall + if wall.data is not None and 'archipack_wall2' in wall.data: + bpy.ops.archipack.wall2_manipulate('EXEC_DEFAULT') + # reselect walls + bpy.ops.object.select_all(action='DESELECT') + for wall in walls: + wall.select = True + context.scene.objects.active = active + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_generate_hole(Operator): + bl_idname = "archipack.generate_hole" + bl_label = "Generate hole" + bl_description = "Generate interactive hole for doors and windows" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + if context.mode == "OBJECT": + manager = ArchipackBoolManager(mode='HYBRID') + o = context.active_object + d = manager.datablock(o) + if d is None: + self.report({'WARNING'}, "Archipack: active object must be a door or a window") + return {'CANCELLED'} + bpy.ops.object.select_all(action='DESELECT') + o.select = True + context.scene.objects.active = o + hole = manager._generate_hole(context, o) + manager.prepare_hole(hole) + hole.select = False + o.select = True + context.scene.objects.active = o + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +def register(): + bpy.utils.register_class(ARCHIPACK_OT_generate_hole) + bpy.utils.register_class(ARCHIPACK_OT_single_boolean) + bpy.utils.register_class(ARCHIPACK_OT_auto_boolean) + + +def unregister(): + bpy.utils.unregister_class(ARCHIPACK_OT_generate_hole) + bpy.utils.unregister_class(ARCHIPACK_OT_single_boolean) + bpy.utils.unregister_class(ARCHIPACK_OT_auto_boolean) diff --git a/archipack/archipack_door.py b/archipack/archipack_door.py new file mode 100644 index 000000000..f29c44d14 --- /dev/null +++ b/archipack/archipack_door.py @@ -0,0 +1,1847 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- + +# noinspection PyUnresolvedReferences +import bpy +# noinspection PyUnresolvedReferences +from bpy.types import Operator, PropertyGroup, Mesh, Panel +from bpy.props import ( + FloatProperty, IntProperty, CollectionProperty, + EnumProperty, BoolProperty, StringProperty + ) +from mathutils import Vector +# door component objects (panels, handles ..) +from .bmesh_utils import BmeshEdit as bmed +from .panel import Panel as DoorPanel +from .materialutils import MaterialUtils +from .archipack_handle import create_handle, door_handle_horizontal_01 +from .archipack_manipulator import Manipulable +from .archipack_preset import ArchipackPreset, PresetMenuOperator +from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchpackDrawTool +from .archipack_gl import FeedbackPanel +from .archipack_keymaps import Keymaps + + +SPACING = 0.005 +BATTUE = 0.01 +BOTTOM_HOLE_MARGIN = 0.001 +FRONT_HOLE_MARGIN = 0.1 + + +def update(self, context): + self.update(context) + + +def update_childs(self, context): + self.update(context, childs_only=True) + + +class archipack_door_panel(ArchipackObject, PropertyGroup): + x = FloatProperty( + name='width', + min=0.25, + default=100.0, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='Width' + ) + y = FloatProperty( + name='Depth', + min=0.001, + default=0.02, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='depth' + ) + z = FloatProperty( + name='height', + min=0.1, + default=2.0, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='height' + ) + direction = IntProperty( + name="Direction", + min=0, + max=1, + description="open direction" + ) + model = IntProperty( + name="model", + min=0, + max=3, + default=0, + description="Model" + ) + chanfer = FloatProperty( + name='chanfer', + min=0.001, + default=0.005, precision=3, + unit='LENGTH', subtype='DISTANCE', + description='chanfer' + ) + panel_spacing = FloatProperty( + name='spacing', + min=0.001, + default=0.1, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='distance between panels' + ) + panel_bottom = FloatProperty( + name='bottom', + min=0.0, + default=0.0, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='distance from bottom' + ) + panel_border = FloatProperty( + name='border', + min=0.001, + default=0.2, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='distance from border' + ) + panels_x = IntProperty( + name="panels h", + min=1, + max=50, + default=1, + description="panels h" + ) + panels_y = IntProperty( + name="panels v", + min=1, + max=50, + default=1, + description="panels v" + ) + panels_distrib = EnumProperty( + name='distribution', + items=( + ('REGULAR', 'Regular', '', 0), + ('ONE_THIRD', '1/3 2/3', '', 1) + ), + default='REGULAR' + ) + handle = EnumProperty( + name='Shape', + items=( + ('NONE', 'No handle', '', 0), + ('BOTH', 'Inside and outside', '', 1) + ), + default='BOTH' + ) + + @property + def panels(self): + + # subdivide side to weld panels + subdiv_x = self.panels_x - 1 + + if self.panels_distrib == 'REGULAR': + subdiv_y = self.panels_y - 1 + else: + subdiv_y = 2 + + # __ y0 + # |__ y1 + # x0 x1 + y0 = -self.y + y1 = 0 + x0 = 0 + x1 = max(0.001, self.panel_border - 0.5 * self.panel_spacing) + + side = DoorPanel( + False, # profil closed + [1, 0, 0, 1], # x index + [x0, x1], + [y0, y0, y1, y1], + [0, 1, 1, 1], # material index + closed_path=True, # + subdiv_x=subdiv_x, + subdiv_y=subdiv_y + ) + + face = None + back = None + + if self.model == 1: + # / y2-y3 + # __/ y1-y0 + # x2 x3 + x2 = 0.5 * self.panel_spacing + x3 = x2 + self.chanfer + y2 = y1 + self.chanfer + y3 = y0 - self.chanfer + + face = DoorPanel( + False, # profil closed + [0, 1, 2], # x index + [0, x2, x3], + [y1, y1, y2], + [1, 1, 1], # material index + side_cap_front=2, # cap index + closed_path=True + ) + + back = DoorPanel( + False, # profil closed + [0, 1, 2], # x index + [x3, x2, 0], + [y3, y0, y0], + [0, 0, 0], # material index + side_cap_back=0, # cap index + closed_path=True + ) + + elif self.model == 2: + # / y2-y3 + # ___ _____/ y1-y0 + # \ / + # \/ y4-y5 + # 0 x2 x4 x5 x6 x3 + x2 = 0.5 * self.panel_spacing + x4 = x2 + self.chanfer + x5 = x4 + self.chanfer + x6 = x5 + 4 * self.chanfer + x3 = x6 + self.chanfer + y2 = y1 - self.chanfer + y4 = y1 + self.chanfer + y3 = y0 + self.chanfer + y5 = y0 - self.chanfer + face = DoorPanel( + False, # profil closed + [0, 1, 2, 3, 4, 5], # x index + [0, x2, x4, x5, x6, x3], + [y1, y1, y4, y1, y1, y2], + [1, 1, 1, 1, 1, 1], # material index + side_cap_front=5, # cap index + closed_path=True + ) + + back = DoorPanel( + False, # profil closed + [0, 1, 2, 3, 4, 5], # x index + [x3, x6, x5, x4, x2, 0], + [y3, y0, y0, y5, y0, y0], + [0, 0, 0, 0, 0, 0], # material index + side_cap_back=0, # cap index + closed_path=True + ) + + elif self.model == 3: + # _____ y2-y3 + # / \ y4-y5 + # __/ y1-y0 + # 0 x2 x3 x4 x5 + x2 = 0.5 * self.panel_spacing + x3 = x2 + self.chanfer + x4 = x3 + 4 * self.chanfer + x5 = x4 + 2 * self.chanfer + y2 = y1 - self.chanfer + y3 = y0 + self.chanfer + y4 = y2 + self.chanfer + y5 = y3 - self.chanfer + face = DoorPanel( + False, # profil closed + [0, 1, 2, 3, 4], # x index + [0, x2, x3, x4, x5], + [y1, y1, y2, y2, y4], + [1, 1, 1, 1, 1], # material index + side_cap_front=4, # cap index + closed_path=True + ) + + back = DoorPanel( + False, # profil closed + [0, 1, 2, 3, 4], # x index + [x5, x4, x3, x2, 0], + [y5, y3, y3, y0, y0], + [0, 0, 0, 0, 0], # material index + side_cap_back=0, # cap index + closed_path=True + ) + + else: + side.side_cap_front = 3 + side.side_cap_back = 0 + + return side, face, back + + @property + def verts(self): + if self.panels_distrib == 'REGULAR': + subdiv_y = self.panels_y - 1 + else: + subdiv_y = 2 + + radius = Vector((0.8, 0.5, 0)) + center = Vector((0, self.z - radius.x, 0)) + + if self.direction == 0: + pivot = 1 + else: + pivot = -1 + + path_type = 'RECTANGLE' + curve_steps = 16 + side, face, back = self.panels + + x1 = max(0.001, self.panel_border - 0.5 * self.panel_spacing) + bottom_z = self.panel_bottom + shape_z = [0, bottom_z, bottom_z, 0] + origin = Vector((-pivot * 0.5 * self.x, 0, 0)) + offset = Vector((0, 0, 0)) + size = Vector((self.x, self.z, 0)) + verts = side.vertices(curve_steps, offset, center, origin, + size, radius, 0, pivot, shape_z=shape_z, path_type=path_type) + if face is not None: + p_radius = radius.copy() + p_radius.x -= x1 + p_radius.y -= x1 + if self.panels_distrib == 'REGULAR': + p_size = Vector(((self.x - 2 * x1) / self.panels_x, + (self.z - 2 * x1 - bottom_z) / self.panels_y, 0)) + for i in range(self.panels_x): + for j in range(self.panels_y): + if j < subdiv_y: + shape = 'RECTANGLE' + else: + shape = path_type + offset = Vector(((pivot * 0.5 * self.x) + p_size.x * (i + 0.5) - 0.5 * size.x + x1, + bottom_z + p_size.y * j + x1, 0)) + origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0)) + verts += face.vertices(curve_steps, offset, center, origin, + p_size, p_radius, 0, 0, shape_z=None, path_type=shape) + if back is not None: + verts += back.vertices(curve_steps, offset, center, origin, + p_size, p_radius, 0, 0, shape_z=None, path_type=shape) + else: + #################################### + # Ratio vertical panels 1/3 - 2/3 + #################################### + p_size = Vector(((self.x - 2 * x1) / self.panels_x, (self.z - 2 * x1 - bottom_z) / 3, 0)) + p_size_2x = Vector((p_size.x, p_size.y * 2, 0)) + for i in range(self.panels_x): + j = 0 + offset = Vector(((pivot * 0.5 * self.x) + p_size.x * (i + 0.5) - 0.5 * size.x + x1, + bottom_z + p_size.y * j + x1, 0)) + origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0)) + shape = 'RECTANGLE' + face.subdiv_y = 0 + verts += face.vertices(curve_steps, offset, center, origin, + p_size, p_radius, 0, 0, shape_z=None, path_type=shape) + if back is not None: + back.subdiv_y = 0 + verts += back.vertices(curve_steps, offset, center, origin, + p_size, p_radius, 0, 0, shape_z=None, path_type=shape) + j = 1 + offset = Vector(((pivot * 0.5 * self.x) + p_size.x * (i + 0.5) - 0.5 * size.x + x1, + bottom_z + p_size.y * j + x1, 0)) + origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, + bottom_z + p_size.y * j + x1, 0)) + shape = path_type + face.subdiv_y = 1 + verts += face.vertices(curve_steps, offset, center, origin, + p_size_2x, p_radius, 0, 0, shape_z=None, path_type=path_type) + if back is not None: + back.subdiv_y = 1 + verts += back.vertices(curve_steps, offset, center, origin, + p_size_2x, p_radius, 0, 0, shape_z=None, path_type=path_type) + + return verts + + @property + def faces(self): + if self.panels_distrib == 'REGULAR': + subdiv_y = self.panels_y - 1 + else: + subdiv_y = 2 + + path_type = 'RECTANGLE' + curve_steps = 16 + side, face, back = self.panels + + faces = side.faces(curve_steps, path_type=path_type) + faces_offset = side.n_verts(curve_steps, path_type=path_type) + + if face is not None: + if self.panels_distrib == 'REGULAR': + for i in range(self.panels_x): + for j in range(self.panels_y): + if j < subdiv_y: + shape = 'RECTANGLE' + else: + shape = path_type + faces += face.faces(curve_steps, path_type=shape, offset=faces_offset) + faces_offset += face.n_verts(curve_steps, path_type=shape) + if back is not None: + faces += back.faces(curve_steps, path_type=shape, offset=faces_offset) + faces_offset += back.n_verts(curve_steps, path_type=shape) + else: + #################################### + # Ratio vertical panels 1/3 - 2/3 + #################################### + for i in range(self.panels_x): + j = 0 + shape = 'RECTANGLE' + face.subdiv_y = 0 + faces += face.faces(curve_steps, path_type=shape, offset=faces_offset) + faces_offset += face.n_verts(curve_steps, path_type=shape) + if back is not None: + back.subdiv_y = 0 + faces += back.faces(curve_steps, path_type=shape, offset=faces_offset) + faces_offset += back.n_verts(curve_steps, path_type=shape) + j = 1 + shape = path_type + face.subdiv_y = 1 + faces += face.faces(curve_steps, path_type=path_type, offset=faces_offset) + faces_offset += face.n_verts(curve_steps, path_type=path_type) + if back is not None: + back.subdiv_y = 1 + faces += back.faces(curve_steps, path_type=path_type, offset=faces_offset) + faces_offset += back.n_verts(curve_steps, path_type=path_type) + + return faces + + @property + def uvs(self): + if self.panels_distrib == 'REGULAR': + subdiv_y = self.panels_y - 1 + else: + subdiv_y = 2 + + radius = Vector((0.8, 0.5, 0)) + center = Vector((0, self.z - radius.x, 0)) + + if self.direction == 0: + pivot = 1 + else: + pivot = -1 + + path_type = 'RECTANGLE' + curve_steps = 16 + side, face, back = self.panels + + x1 = max(0.001, self.panel_border - 0.5 * self.panel_spacing) + bottom_z = self.panel_bottom + origin = Vector((-pivot * 0.5 * self.x, 0, 0)) + size = Vector((self.x, self.z, 0)) + uvs = side.uv(curve_steps, center, origin, size, radius, 0, pivot, 0, self.panel_border, path_type=path_type) + if face is not None: + p_radius = radius.copy() + p_radius.x -= x1 + p_radius.y -= x1 + if self.panels_distrib == 'REGULAR': + p_size = Vector(((self.x - 2 * x1) / self.panels_x, (self.z - 2 * x1 - bottom_z) / self.panels_y, 0)) + for i in range(self.panels_x): + for j in range(self.panels_y): + if j < subdiv_y: + shape = 'RECTANGLE' + else: + shape = path_type + origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0)) + uvs += face.uv(curve_steps, center, origin, p_size, p_radius, 0, 0, 0, 0, path_type=shape) + if back is not None: + uvs += back.uv(curve_steps, center, origin, + p_size, p_radius, 0, 0, 0, 0, path_type=shape) + else: + #################################### + # Ratio vertical panels 1/3 - 2/3 + #################################### + p_size = Vector(((self.x - 2 * x1) / self.panels_x, (self.z - 2 * x1 - bottom_z) / 3, 0)) + p_size_2x = Vector((p_size.x, p_size.y * 2, 0)) + for i in range(self.panels_x): + j = 0 + origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0)) + shape = 'RECTANGLE' + face.subdiv_y = 0 + uvs += face.uv(curve_steps, center, origin, p_size, p_radius, 0, 0, 0, 0, path_type=shape) + if back is not None: + back.subdiv_y = 0 + uvs += back.uv(curve_steps, center, origin, p_size, p_radius, 0, 0, 0, 0, path_type=shape) + j = 1 + origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0)) + shape = path_type + face.subdiv_y = 1 + uvs += face.uv(curve_steps, center, origin, p_size_2x, p_radius, 0, 0, 0, 0, path_type=path_type) + if back is not None: + back.subdiv_y = 1 + uvs += back.uv(curve_steps, center, origin, + p_size_2x, p_radius, 0, 0, 0, 0, path_type=path_type) + return uvs + + @property + def matids(self): + if self.panels_distrib == 'REGULAR': + subdiv_y = self.panels_y - 1 + else: + subdiv_y = 2 + + path_type = 'RECTANGLE' + curve_steps = 16 + side, face, back = self.panels + + mat = side.mat(curve_steps, 1, 0, path_type=path_type) + + if face is not None: + if self.panels_distrib == 'REGULAR': + for i in range(self.panels_x): + for j in range(self.panels_y): + if j < subdiv_y: + shape = 'RECTANGLE' + else: + shape = path_type + mat += face.mat(curve_steps, 1, 1, path_type=shape) + if back is not None: + mat += back.mat(curve_steps, 0, 0, path_type=shape) + else: + #################################### + # Ratio vertical panels 1/3 - 2/3 + #################################### + for i in range(self.panels_x): + j = 0 + shape = 'RECTANGLE' + face.subdiv_y = 0 + mat += face.mat(curve_steps, 1, 1, path_type=shape) + if back is not None: + back.subdiv_y = 0 + mat += back.mat(curve_steps, 0, 0, path_type=shape) + j = 1 + shape = path_type + face.subdiv_y = 1 + mat += face.mat(curve_steps, 1, 1, path_type=shape) + if back is not None: + back.subdiv_y = 1 + mat += back.mat(curve_steps, 0, 0, path_type=shape) + return mat + + def find_handle(self, o): + for child in o.children: + if 'archipack_handle' in child: + return child + return None + + def update_handle(self, context, o): + handle = self.find_handle(o) + if handle is None: + m = bpy.data.meshes.new("Handle") + handle = create_handle(context, o, m) + MaterialUtils.add_handle_materials(handle) + verts, faces = door_handle_horizontal_01(self.direction, 1) + b_verts, b_faces = door_handle_horizontal_01(self.direction, 0, offset=len(verts)) + b_verts = [(v[0], v[1] - self.y, v[2]) for v in b_verts] + handle_y = 0.07 + handle.location = ((1 - self.direction * 2) * (self.x - handle_y), 0, 0.5 * self.z) + bmed.buildmesh(context, handle, verts + b_verts, faces + b_faces) + + def remove_handle(self, context, o): + handle = self.find_handle(o) + if handle is not None: + context.scene.objects.unlink(handle) + bpy.data.objects.remove(handle, do_unlink=True) + + def update(self, context): + o = self.find_in_selection(context) + + if o is None: + return + + bmed.buildmesh(context, o, self.verts, self.faces, matids=self.matids, uvs=self.uvs, weld=True) + + if self.handle == 'NONE': + self.remove_handle(context, o) + else: + self.update_handle(context, o) + + self.restore_context(context) + + +class ARCHIPACK_PT_door_panel(Panel): + bl_idname = "ARCHIPACK_PT_door_panel" + bl_label = "Door" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + # bl_context = 'object' + bl_category = 'ArchiPack' + + @classmethod + def poll(cls, context): + return archipack_door_panel.filter(context.active_object) + + def draw(self, context): + layout = self.layout + layout.operator("archipack.select_parent") + + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_door_panel(Operator): + bl_idname = "archipack.door_panel" + bl_label = "Door model 1" + bl_description = "Door model 1" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + x = FloatProperty( + name='width', + min=0.1, + default=0.80, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='Width' + ) + z = FloatProperty( + name='height', + min=0.1, + default=2.0, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='height' + ) + y = FloatProperty( + name='depth', + min=0.001, + default=0.02, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='Depth' + ) + direction = IntProperty( + name="direction", + min=0, + max=1, + description="open direction" + ) + model = IntProperty( + name="model", + min=0, + max=3, + description="panel type" + ) + chanfer = FloatProperty( + name='chanfer', + min=0.001, + default=0.005, precision=3, + unit='LENGTH', subtype='DISTANCE', + description='chanfer' + ) + panel_spacing = FloatProperty( + name='spacing', + min=0.001, + default=0.1, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='distance between panels' + ) + panel_bottom = FloatProperty( + name='bottom', + min=0.0, + default=0.0, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='distance from bottom' + ) + panel_border = FloatProperty( + name='border', + min=0.001, + default=0.2, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='distance from border' + ) + panels_x = IntProperty( + name="panels h", + min=1, + max=50, + default=1, + description="panels h" + ) + panels_y = IntProperty( + name="panels v", + min=1, + max=50, + default=1, + description="panels v" + ) + panels_distrib = EnumProperty( + name='distribution', + items=( + ('REGULAR', 'Regular', '', 0), + ('ONE_THIRD', '1/3 2/3', '', 1) + ), + default='REGULAR' + ) + handle = EnumProperty( + name='Shape', + items=( + ('NONE', 'No handle', '', 0), + ('BOTH', 'Inside and outside', '', 1) + ), + default='BOTH' + ) + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def create(self, context): + """ + expose only basic params in operator + use object property for other params + """ + m = bpy.data.meshes.new("Panel") + o = bpy.data.objects.new("Panel", m) + d = m.archipack_door_panel.add() + d.x = self.x + d.y = self.y + d.z = self.z + d.model = self.model + d.direction = self.direction + d.chanfer = self.chanfer + d.panel_border = self.panel_border + d.panel_bottom = self.panel_bottom + d.panel_spacing = self.panel_spacing + d.panels_distrib = self.panels_distrib + d.panels_x = self.panels_x + d.panels_y = self.panels_y + d.handle = self.handle + context.scene.objects.link(o) + o.lock_location[0] = True + o.lock_location[1] = True + o.lock_location[2] = True + o.lock_rotation[0] = True + o.lock_rotation[1] = True + o.lock_scale[0] = True + o.lock_scale[1] = True + o.lock_scale[2] = True + o.select = True + context.scene.objects.active = o + d.update(context) + MaterialUtils.add_door_materials(o) + o.lock_rotation[0] = True + o.lock_rotation[1] = True + return o + + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.select = True + context.scene.objects.active = o + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_select_parent(Operator): + bl_idname = "archipack.select_parent" + bl_label = "Edit parameters" + bl_description = "Edit parameters located on parent" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def execute(self, context): + if context.mode == "OBJECT": + if context.active_object is not None and context.active_object.parent is not None: + bpy.ops.object.select_all(action="DESELECT") + context.active_object.parent.select = True + context.scene.objects.active = context.active_object.parent + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class archipack_door(ArchipackObject, Manipulable, PropertyGroup): + """ + The frame is the door main object + parent parametric object + create/remove/update her own childs + """ + x = FloatProperty( + name='width', + min=0.25, + default=100.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='Width', update=update, + ) + y = FloatProperty( + name='depth', + min=0.1, + default=0.20, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='Depth', update=update, + ) + z = FloatProperty( + name='height', + min=0.1, + default=2.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='height', update=update, + ) + frame_x = FloatProperty( + name='Width', + min=0, + default=0.1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='frame width', update=update, + ) + frame_y = FloatProperty( + name='Depth', + default=0.03, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='frame depth', update=update, + ) + direction = IntProperty( + name="Direction", + min=0, + max=1, + description="open direction", update=update, + ) + door_y = FloatProperty( + name='Depth', + min=0.001, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='depth', update=update, + ) + door_offset = FloatProperty( + name='Offset', + min=0, + default=0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='offset', update=update, + ) + model = IntProperty( + name="Model", + min=0, + max=3, + default=0, + description="Model", update=update, + ) + n_panels = IntProperty( + name="Panels", + min=1, + max=2, + default=1, + description="number of panels", update=update + ) + chanfer = FloatProperty( + name='chanfer', + min=0.001, + default=0.005, precision=3, step=0.01, + unit='LENGTH', subtype='DISTANCE', + description='chanfer', update=update_childs, + ) + panel_spacing = FloatProperty( + name='spacing', + min=0.001, + default=0.1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='distance between panels', update=update_childs, + ) + panel_bottom = FloatProperty( + name='bottom', + min=0.0, + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='distance from bottom', update=update_childs, + ) + panel_border = FloatProperty( + name='border', + min=0.001, + default=0.2, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='distance from border', update=update_childs, + ) + panels_x = IntProperty( + name="panels h", + min=1, + max=50, + default=1, + description="panels h", update=update_childs, + ) + panels_y = IntProperty( + name="panels v", + min=1, + max=50, + default=1, + description="panels v", update=update_childs, + ) + panels_distrib = EnumProperty( + name='distribution', + items=( + ('REGULAR', 'Regular', '', 0), + ('ONE_THIRD', '1/3 2/3', '', 1) + ), + default='REGULAR', update=update_childs, + ) + handle = EnumProperty( + name='Handle', + items=( + ('NONE', 'No handle', '', 0), + ('BOTH', 'Inside and outside', '', 1) + ), + default='BOTH', update=update_childs, + ) + hole_margin = FloatProperty( + name='hole margin', + min=0.0, + default=0.1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='how much hole surround wall' + ) + flip = BoolProperty( + default=False, + update=update, + description='flip outside and outside material of hole' + ) + auto_update = BoolProperty( + options={'SKIP_SAVE'}, + default=True, + update=update + ) + + @property + def frame(self): + + # + # _____ y0 + # | |___ y1 + # x | y3 + # | | + # |_________| y2 + # + # x2 x1 x0 + x0 = 0 + x1 = -BATTUE + x2 = -self.frame_x + y0 = max(0.25 * self.door_y + 0.0005, self.y / 2 + self.frame_y) + y1 = max(y0 - 0.5 * self.door_y - self.door_offset, -y0 + 0.001) + y2 = -y0 + y3 = 0 + return DoorPanel( + True, # closed + [0, 0, 0, 1, 1, 2, 2], # x index + [x2, x1, x0], + [y2, y3, y0, y0, y1, y1, y2], + [0, 1, 1, 1, 1, 0, 0], # material index + closed_path=False + ) + + @property + def hole(self): + # + # _____ y0 + # | + # x y2 + # | + # |_____ y1 + # + # x0 + x0 = 0 + y0 = self.y / 2 + self.hole_margin + y1 = -y0 + y2 = 0 + outside_mat = 0 + inside_mat = 1 + if self.flip: + outside_mat, inside_mat = inside_mat, outside_mat + return DoorPanel( + False, # closed + [0, 0, 0], # x index + [x0], + [y1, y2, y0], + [outside_mat, inside_mat, inside_mat], # material index + closed_path=True, + side_cap_front=2, + side_cap_back=0 # cap index + ) + + @property + def verts(self): + # door inner space + v = Vector((0, 0, 0)) + size = Vector((self.x, self.z, self.y)) + return self.frame.vertices(16, v, v, v, size, v, 0, 0, shape_z=None, path_type='RECTANGLE') + + @property + def faces(self): + return self.frame.faces(16, path_type='RECTANGLE') + + @property + def matids(self): + return self.frame.mat(16, 0, 0, path_type='RECTANGLE') + + @property + def uvs(self): + v = Vector((0, 0, 0)) + size = Vector((self.x, self.z, self.y)) + return self.frame.uv(16, v, v, size, v, 0, 0, 0, 0, path_type='RECTANGLE') + + def setup_manipulators(self): + if len(self.manipulators) == 3: + return + s = self.manipulators.add() + s.prop1_name = "x" + s.prop2_name = "x" + s.type_key = "SNAP_SIZE_LOC" + s = self.manipulators.add() + s.prop1_name = "y" + s.prop2_name = "y" + s.type_key = "SNAP_SIZE_LOC" + s = self.manipulators.add() + s.prop1_name = "z" + s.normal = Vector((0, 1, 0)) + + def remove_childs(self, context, o, to_remove): + for child in o.children: + if to_remove < 1: + return + if archipack_door_panel.filter(child): + self.remove_handle(context, child) + to_remove -= 1 + context.scene.objects.unlink(child) + bpy.data.objects.remove(child, do_unlink=True) + + def remove_handle(self, context, o): + handle = self.find_handle(o) + if handle is not None: + context.scene.objects.unlink(handle) + bpy.data.objects.remove(handle, do_unlink=True) + + def create_childs(self, context, o): + + n_childs = 0 + for child in o.children: + if archipack_door_panel.filter(child): + n_childs += 1 + + # remove child + if n_childs > self.n_panels: + self.remove_childs(context, o, n_childs - self.n_panels) + + if n_childs < 1: + # create one door panel + bpy.ops.archipack.door_panel(x=self.x, z=self.z, door_y=self.door_y, + n_panels=self.n_panels, direction=self.direction) + child = context.active_object + child.parent = o + child.matrix_world = o.matrix_world.copy() + location = self.x / 2 + BATTUE - SPACING + if self.direction == 0: + location = -location + child.location.x = location + child.location.y = self.door_y + + if self.n_panels == 2 and n_childs < 2: + # create 2nth door panel + bpy.ops.archipack.door_panel(x=self.x, z=self.z, door_y=self.door_y, + n_panels=self.n_panels, direction=1 - self.direction) + child = context.active_object + child.parent = o + child.matrix_world = o.matrix_world.copy() + location = self.x / 2 + BATTUE - SPACING + if self.direction == 1: + location = -location + child.location.x = location + child.location.y = self.door_y + + def find_handle(self, o): + for handle in o.children: + if 'archipack_handle' in handle: + return handle + return None + + def get_childs_panels(self, context, o): + return [child for child in o.children if archipack_door_panel.filter(child)] + + def _synch_childs(self, context, o, linked, childs): + """ + sub synch childs nodes of linked object + """ + # remove childs not found on source + l_childs = self.get_childs_panels(context, linked) + c_names = [c.data.name for c in childs] + for c in l_childs: + try: + id = c_names.index(c.data.name) + except: + self.remove_handle(context, c) + context.scene.objects.unlink(c) + bpy.data.objects.remove(c, do_unlink=True) + + # children ordering may not be the same, so get the right l_childs order + l_childs = self.get_childs_panels(context, linked) + l_names = [c.data.name for c in l_childs] + order = [] + for c in childs: + try: + id = l_names.index(c.data.name) + except: + id = -1 + order.append(id) + + # add missing childs and update other ones + for i, child in enumerate(childs): + if order[i] < 0: + p = bpy.data.objects.new("DoorPanel", child.data) + context.scene.objects.link(p) + p.lock_location[0] = True + p.lock_location[1] = True + p.lock_location[2] = True + p.lock_rotation[0] = True + p.lock_rotation[1] = True + p.lock_scale[0] = True + p.lock_scale[1] = True + p.lock_scale[2] = True + p.parent = linked + p.matrix_world = linked.matrix_world.copy() + p.location = child.location.copy() + else: + p = l_childs[order[i]] + + p.location = child.location.copy() + + # update handle + handle = self.find_handle(child) + h = self.find_handle(p) + if handle is not None: + if h is None: + h = create_handle(context, p, handle.data) + MaterialUtils.add_handle_materials(h) + h.location = handle.location.copy() + elif h is not None: + context.scene.objects.unlink(h) + bpy.data.objects.remove(h, do_unlink=True) + + def _synch_hole(self, context, linked, hole): + l_hole = self.find_hole(linked) + if l_hole is None: + l_hole = bpy.data.objects.new("hole", hole.data) + l_hole['archipack_hole'] = True + context.scene.objects.link(l_hole) + l_hole.parent = linked + l_hole.matrix_world = linked.matrix_world.copy() + l_hole.location = hole.location.copy() + else: + l_hole.data = hole.data + + def synch_childs(self, context, o): + """ + synch childs nodes of linked objects + """ + bpy.ops.object.select_all(action='DESELECT') + o.select = True + context.scene.objects.active = o + childs = self.get_childs_panels(context, o) + hole = self.find_hole(o) + bpy.ops.object.select_linked(type='OBDATA') + for linked in context.selected_objects: + if linked != o: + self._synch_childs(context, o, linked, childs) + if hole is not None: + self._synch_hole(context, linked, hole) + + def update_childs(self, context, o): + """ + pass params to childrens + """ + childs = self.get_childs_panels(context, o) + n_childs = len(childs) + self.remove_childs(context, o, n_childs - self.n_panels) + + childs = self.get_childs_panels(context, o) + n_childs = len(childs) + child_n = 0 + + # location_y = self.y / 2 + self.frame_y - SPACING + # location_y = min(max(self.door_offset, - location_y), location_y) + self.door_y + + location_y = max(0.25 * self.door_y + 0.0005, self.y / 2 + self.frame_y) + location_y = max(location_y - self.door_offset + 0.5 * self.door_y, -location_y + self.door_y + 0.001) + + x = self.x / self.n_panels + (3 - self.n_panels) * (BATTUE - SPACING) + y = self.door_y + z = self.z + BATTUE - SPACING + + if self.n_panels < 2: + direction = self.direction + else: + direction = 0 + + for panel in range(self.n_panels): + child_n += 1 + + if child_n == 1: + handle = self.handle + else: + handle = 'NONE' + + if child_n > 1: + direction = 1 - direction + + location_x = (2 * direction - 1) * (self.x / 2 + BATTUE - SPACING) + + if child_n > n_childs: + bpy.ops.archipack.door_panel( + x=x, + y=y, + z=z, + model=self.model, + direction=direction, + chanfer=self.chanfer, + panel_border=self.panel_border, + panel_bottom=self.panel_bottom, + panel_spacing=self.panel_spacing, + panels_distrib=self.panels_distrib, + panels_x=self.panels_x, + panels_y=self.panels_y, + handle=handle + ) + child = context.active_object + # parenting at 0, 0, 0 before set object matrix_world + # so location remains local from frame + child.parent = o + child.matrix_world = o.matrix_world.copy() + else: + child = childs[child_n - 1] + child.select = True + context.scene.objects.active = child + props = archipack_door_panel.datablock(child) + if props is not None: + props.x = x + props.y = y + props.z = z + props.model = self.model + props.direction = direction + props.chanfer = self.chanfer + props.panel_border = self.panel_border + props.panel_bottom = self.panel_bottom + props.panel_spacing = self.panel_spacing + props.panels_distrib = self.panels_distrib + props.panels_x = self.panels_x + props.panels_y = self.panels_y + props.handle = handle + props.update(context) + child.location = Vector((location_x, location_y, 0)) + + def update(self, context, childs_only=False): + + # support for "copy to selected" + o = self.find_in_selection(context, self.auto_update) + + if o is None: + return + + self.setup_manipulators() + + if childs_only is False: + bmed.buildmesh(context, o, self.verts, self.faces, self.matids, self.uvs) + + self.update_childs(context, o) + + if childs_only is False and self.find_hole(o) is not None: + self.interactive_hole(context, o) + + # support for instances childs, update at object level + self.synch_childs(context, o) + + # setup 3d points for gl manipulators + x, y = 0.5 * self.x, 0.5 * self.y + self.manipulators[0].set_pts([(-x, -y, 0), (x, -y, 0), (1, 0, 0)]) + self.manipulators[1].set_pts([(-x, -y, 0), (-x, y, 0), (-1, 0, 0)]) + self.manipulators[2].set_pts([(x, -y, 0), (x, -y, self.z), (-1, 0, 0)]) + + # restore context + self.restore_context(context) + + def find_hole(self, o): + for child in o.children: + if 'archipack_hole' in child: + return child + return None + + def interactive_hole(self, context, o): + hole_obj = self.find_hole(o) + if hole_obj is None: + m = bpy.data.meshes.new("hole") + hole_obj = bpy.data.objects.new("hole", m) + context.scene.objects.link(hole_obj) + hole_obj['archipack_hole'] = True + hole_obj.parent = o + hole_obj.matrix_world = o.matrix_world.copy() + MaterialUtils.add_wall2_materials(hole_obj) + hole = self.hole + v = Vector((0, 0, 0)) + offset = Vector((0, -0.001, 0)) + size = Vector((self.x + 2 * self.frame_x, self.z + self.frame_x + 0.001, self.y)) + verts = hole.vertices(16, offset, v, v, size, v, 0, 0, shape_z=None, path_type='RECTANGLE') + faces = hole.faces(16, path_type='RECTANGLE') + matids = hole.mat(16, 0, 1, path_type='RECTANGLE') + uvs = hole.uv(16, v, v, size, v, 0, 0, 0, 0, path_type='RECTANGLE') + bmed.buildmesh(context, hole_obj, verts, faces, matids=matids, uvs=uvs) + return hole_obj + + def robust_hole(self, context, tM): + hole = self.hole + m = bpy.data.meshes.new("hole") + o = bpy.data.objects.new("hole", m) + o['archipack_robusthole'] = True + context.scene.objects.link(o) + v = Vector((0, 0, 0)) + offset = Vector((0, -0.001, 0)) + size = Vector((self.x + 2 * self.frame_x, self.z + self.frame_x + 0.001, self.y)) + verts = hole.vertices(16, offset, v, v, size, v, 0, 0, shape_z=None, path_type='RECTANGLE') + verts = [tM * Vector(v) for v in verts] + faces = hole.faces(16, path_type='RECTANGLE') + matids = hole.mat(16, 0, 1, path_type='RECTANGLE') + uvs = hole.uv(16, v, v, size, v, 0, 0, 0, 0, path_type='RECTANGLE') + bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs) + MaterialUtils.add_wall2_materials(o) + o.select = True + context.scene.objects.active = o + return o + + +class ARCHIPACK_PT_door(Panel): + bl_idname = "ARCHIPACK_PT_door" + bl_label = "Door" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'ArchiPack' + + @classmethod + def poll(cls, context): + return archipack_door.filter(context.active_object) + + def draw(self, context): + o = context.active_object + if not archipack_door.filter(o): + return + layout = self.layout + layout.operator('archipack.door_manipulate', icon='HAND') + props = archipack_door.datablock(o) + row = layout.row(align=True) + row.operator('archipack.door', text="Refresh", icon='FILE_REFRESH').mode = 'REFRESH' + if o.data.users > 1: + row.operator('archipack.door', text="Make unique", icon='UNLINKED').mode = 'UNIQUE' + row.operator('archipack.door', text="Delete", icon='ERROR').mode = 'DELETE' + box = layout.box() + # box.label(text="Styles") + row = box.row(align=True) + row.operator("archipack.door_preset_menu", text=bpy.types.ARCHIPACK_OT_door_preset_menu.bl_label) + row.operator("archipack.door_preset", text="", icon='ZOOMIN') + row.operator("archipack.door_preset", text="", icon='ZOOMOUT').remove_active = True + row = layout.row() + box = row.box() + box.label(text="Size") + box.prop(props, 'x') + box.prop(props, 'y') + box.prop(props, 'z') + box.prop(props, 'door_offset') + row = layout.row() + box = row.box() + row = box.row() + row.label(text="Door") + box.prop(props, 'direction') + box.prop(props, 'n_panels') + box.prop(props, 'door_y') + box.prop(props, 'handle') + row = layout.row() + box = row.box() + row = box.row() + row.label(text="Frame") + row = box.row(align=True) + row.prop(props, 'frame_x') + row.prop(props, 'frame_y') + row = layout.row() + box = row.box() + row = box.row() + row.label(text="Panels") + box.prop(props, 'model') + if props.model > 0: + box.prop(props, 'panels_distrib', text="") + row = box.row(align=True) + row.prop(props, 'panels_x') + if props.panels_distrib == 'REGULAR': + row.prop(props, 'panels_y') + box.prop(props, 'panel_bottom') + box.prop(props, 'panel_spacing') + box.prop(props, 'panel_border') + box.prop(props, 'chanfer') + + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_door(ArchipackCreateTool, Operator): + bl_idname = "archipack.door" + bl_label = "Door" + bl_description = "Door" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + x = FloatProperty( + name='width', + min=0.1, + default=0.80, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='Width' + ) + y = FloatProperty( + name='depth', + min=0.1, + default=0.20, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='Depth' + ) + z = FloatProperty( + name='height', + min=0.1, + default=2.0, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='height' + ) + direction = IntProperty( + name="direction", + min=0, + max=1, + description="open direction" + ) + n_panels = IntProperty( + name="panels", + min=1, + max=2, + default=1, + description="number of panels" + ) + chanfer = FloatProperty( + name='chanfer', + min=0.001, + default=0.005, precision=3, + unit='LENGTH', subtype='DISTANCE', + description='chanfer' + ) + panel_spacing = FloatProperty( + name='spacing', + min=0.001, + default=0.1, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='distance between panels' + ) + panel_bottom = FloatProperty( + name='bottom', + default=0.0, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='distance from bottom' + ) + panel_border = FloatProperty( + name='border', + min=0.001, + default=0.2, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='distance from border' + ) + panels_x = IntProperty( + name="panels h", + min=1, + max=50, + default=1, + description="panels h" + ) + panels_y = IntProperty( + name="panels v", + min=1, + max=50, + default=1, + description="panels v" + ) + panels_distrib = EnumProperty( + name='distribution', + items=( + ('REGULAR', 'Regular', '', 0), + ('ONE_THIRD', '1/3 2/3', '', 1) + ), + default='REGULAR' + ) + handle = EnumProperty( + name='Shape', + items=( + ('NONE', 'No handle', '', 0), + ('BOTH', 'Inside and outside', '', 1) + ), + default='BOTH' + ) + mode = EnumProperty( + items=( + ('CREATE', 'Create', '', 0), + ('DELETE', 'Delete', '', 1), + ('REFRESH', 'Refresh', '', 2), + ('UNIQUE', 'Make unique', '', 3), + ), + default='CREATE' + ) + + def create(self, context): + """ + expose only basic params in operator + use object property for other params + """ + m = bpy.data.meshes.new("Door") + o = bpy.data.objects.new("Door", m) + d = m.archipack_door.add() + d.x = self.x + d.y = self.y + d.z = self.z + d.direction = self.direction + d.n_panels = self.n_panels + d.chanfer = self.chanfer + d.panel_border = self.panel_border + d.panel_bottom = self.panel_bottom + d.panel_spacing = self.panel_spacing + d.panels_distrib = self.panels_distrib + d.panels_x = self.panels_x + d.panels_y = self.panels_y + d.handle = self.handle + context.scene.objects.link(o) + o.select = True + context.scene.objects.active = o + self.load_preset(d) + self.add_material(o) + o.select = True + context.scene.objects.active = o + return o + + def delete(self, context): + o = context.active_object + if archipack_door.filter(o): + bpy.ops.archipack.disable_manipulate() + for child in o.children: + if 'archipack_hole' in child: + context.scene.objects.unlink(child) + bpy.data.objects.remove(child, do_unlink=True) + elif child.data is not None and 'archipack_door_panel' in child.data: + for handle in child.children: + if 'archipack_handle' in handle: + context.scene.objects.unlink(handle) + bpy.data.objects.remove(handle, do_unlink=True) + context.scene.objects.unlink(child) + bpy.data.objects.remove(child, do_unlink=True) + context.scene.objects.unlink(o) + bpy.data.objects.remove(o, do_unlink=True) + + def update(self, context): + o = context.active_object + d = archipack_door.datablock(o) + if d is not None: + d.update(context) + bpy.ops.object.select_linked(type='OBDATA') + for linked in context.selected_objects: + if linked != o: + archipack_door.datablock(linked).update(context) + bpy.ops.object.select_all(action="DESELECT") + o.select = True + context.scene.objects.active = o + + def unique(self, context): + act = context.active_object + sel = [o for o in context.selected_objects] + bpy.ops.object.select_all(action="DESELECT") + for o in sel: + if archipack_door.filter(o): + o.select = True + for child in o.children: + if 'archipack_hole' in child or (child.data is not None and + 'archipack_door_panel' in child.data): + child.hide_select = False + child.select = True + if len(context.selected_objects) > 0: + bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, + obdata=True, material=False, texture=False, animation=False) + for child in context.selected_objects: + if 'archipack_hole' in child: + child.hide_select = True + bpy.ops.object.select_all(action="DESELECT") + context.scene.objects.active = act + for o in sel: + o.select = True + + def execute(self, context): + if context.mode == "OBJECT": + if self.mode == 'CREATE': + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.location = bpy.context.scene.cursor_location + o.select = True + context.scene.objects.active = o + self.manipulate() + elif self.mode == 'DELETE': + self.delete(context) + elif self.mode == 'REFRESH': + self.update(context) + elif self.mode == 'UNIQUE': + self.unique(context) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator): + bl_idname = "archipack.door_draw" + bl_label = "Draw Doors" + bl_description = "Draw Doors over walls" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + filepath = StringProperty(default="") + feedback = None + stack = [] + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def draw_callback(self, _self, context): + self.feedback.draw(context) + + def add_object(self, context, event): + o = context.active_object + bpy.ops.object.select_all(action="DESELECT") + + if archipack_door.filter(o): + + o.select = True + context.scene.objects.active = o + + if event.shift: + bpy.ops.archipack.door(mode="UNIQUE") + + new_w = o.copy() + new_w.data = o.data + context.scene.objects.link(new_w) + + o = new_w + o.select = True + context.scene.objects.active = o + + # synch subs from parent instance + bpy.ops.archipack.door(mode="REFRESH") + + else: + bpy.ops.archipack.door(auto_manipulate=False, filepath=self.filepath) + o = context.active_object + + bpy.ops.archipack.generate_hole('INVOKE_DEFAULT') + o.select = True + context.scene.objects.active = o + + def modal(self, context, event): + + context.area.tag_redraw() + o = context.active_object + d = archipack_door.datablock(o) + hole = None + + if d is not None: + hole = d.find_hole(o) + + # hide hole from raycast + if hole is not None: + o.hide = True + hole.hide = True + + res, tM, wall, y = self.mouse_hover_wall(context, event) + + if hole is not None: + o.hide = False + hole.hide = False + + if res and d is not None: + o.matrix_world = tM + if d.y != wall.data.archipack_wall2[0].width: + d.y = wall.data.archipack_wall2[0].width + + if event.value == 'PRESS': + if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}: + if wall is not None: + context.scene.objects.active = wall + wall.select = True + if bpy.ops.archipack.single_boolean.poll(): + bpy.ops.archipack.single_boolean() + wall.select = False + # o must be a door here + if d is not None: + context.scene.objects.active = o + self.stack.append(o) + self.add_object(context, event) + context.active_object.matrix_world = tM + return {'RUNNING_MODAL'} + + # prevent selection of other object + if event.type in {'RIGHTMOUSE'}: + return {'RUNNING_MODAL'} + + if self.keymap.check(event, self.keymap.undo) or ( + event.type in {'BACK_SPACE'} and event.value == 'RELEASE' + ): + if len(self.stack) > 0: + last = self.stack.pop() + context.scene.objects.active = last + bpy.ops.archipack.door(mode="DELETE") + context.scene.objects.active = o + return {'RUNNING_MODAL'} + + if event.value == 'RELEASE': + + if event.type in {'ESC', 'RIGHTMOUSE'}: + bpy.ops.archipack.door(mode='DELETE') + self.feedback.disable() + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + return {'FINISHED'} + + return {'PASS_THROUGH'} + + def invoke(self, context, event): + + if context.mode == "OBJECT": + o = None + self.stack = [] + self.keymap = Keymaps(context) + # exit manipulate_mode if any + bpy.ops.archipack.disable_manipulate() + # invoke with alt pressed will use current object as basis for linked copy + if self.filepath == '' and archipack_door.filter(context.active_object): + o = context.active_object + context.scene.objects.active = None + bpy.ops.object.select_all(action="DESELECT") + if o is not None: + o.select = True + context.scene.objects.active = o + self.add_object(context, event) + self.feedback = FeedbackPanel() + self.feedback.instructions(context, "Draw a door", "Click & Drag over a wall", [ + ('LEFTCLICK, RET, SPACE, ENTER', 'Create a door'), + ('BACKSPACE, CTRL+Z', 'undo last'), + ('SHIFT', 'Make independant copy'), + ('RIGHTCLICK or ESC', 'exit') + ]) + self.feedback.enable() + args = (self, context) + + self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback, args, 'WINDOW', 'POST_PIXEL') + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +# ------------------------------------------------------------------ +# Define operator class to manipulate object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_door_manipulate(Operator): + bl_idname = "archipack.door_manipulate" + bl_label = "Manipulate" + bl_description = "Manipulate" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_door.filter(context.active_object) + + def invoke(self, context, event): + d = archipack_door.datablock(context.active_object) + d.manipulable_invoke(context) + return {'FINISHED'} + + +# ------------------------------------------------------------------ +# Define operator class to load / save presets +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_door_preset_menu(PresetMenuOperator, Operator): + bl_description = "Show Doors presets" + bl_idname = "archipack.door_preset_menu" + bl_label = "Door Presets" + preset_subdir = "archipack_door" + + +class ARCHIPACK_OT_door_preset(ArchipackPreset, Operator): + """Add a Door Preset""" + bl_idname = "archipack.door_preset" + bl_label = "Add Door Preset" + preset_menu = "ARCHIPACK_OT_door_preset_menu" + + @property + def blacklist(self): + # 'x', 'y', 'z', 'direction', + return ['manipulators'] + + +def register(): + bpy.utils.register_class(archipack_door_panel) + Mesh.archipack_door_panel = CollectionProperty(type=archipack_door_panel) + bpy.utils.register_class(ARCHIPACK_PT_door_panel) + bpy.utils.register_class(ARCHIPACK_OT_door_panel) + bpy.utils.register_class(ARCHIPACK_OT_select_parent) + bpy.utils.register_class(archipack_door) + Mesh.archipack_door = CollectionProperty(type=archipack_door) + bpy.utils.register_class(ARCHIPACK_OT_door_preset_menu) + bpy.utils.register_class(ARCHIPACK_PT_door) + bpy.utils.register_class(ARCHIPACK_OT_door) + bpy.utils.register_class(ARCHIPACK_OT_door_preset) + bpy.utils.register_class(ARCHIPACK_OT_door_draw) + bpy.utils.register_class(ARCHIPACK_OT_door_manipulate) + + +def unregister(): + bpy.utils.unregister_class(archipack_door_panel) + del Mesh.archipack_door_panel + bpy.utils.unregister_class(ARCHIPACK_PT_door_panel) + bpy.utils.unregister_class(ARCHIPACK_OT_door_panel) + bpy.utils.unregister_class(ARCHIPACK_OT_select_parent) + bpy.utils.unregister_class(archipack_door) + del Mesh.archipack_door + bpy.utils.unregister_class(ARCHIPACK_OT_door_preset_menu) + bpy.utils.unregister_class(ARCHIPACK_PT_door) + bpy.utils.unregister_class(ARCHIPACK_OT_door) + bpy.utils.unregister_class(ARCHIPACK_OT_door_preset) + bpy.utils.unregister_class(ARCHIPACK_OT_door_draw) + bpy.utils.unregister_class(ARCHIPACK_OT_door_manipulate) diff --git a/archipack/archipack_fence.py b/archipack/archipack_fence.py new file mode 100644 index 000000000..961b516eb --- /dev/null +++ b/archipack/archipack_fence.py @@ -0,0 +1,1782 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +# noinspection PyUnresolvedReferences +import bpy +# noinspection PyUnresolvedReferences +from bpy.types import Operator, PropertyGroup, Mesh, Panel +from bpy.props import ( + FloatProperty, BoolProperty, IntProperty, CollectionProperty, + StringProperty, EnumProperty, FloatVectorProperty + ) +from .bmesh_utils import BmeshEdit as bmed +from .panel import Panel as Lofter +from mathutils import Vector, Matrix +from mathutils.geometry import interpolate_bezier +from math import sin, cos, pi, acos, atan2 +from .archipack_manipulator import Manipulable, archipack_manipulator +from .archipack_2d import Line, Arc +from .archipack_preset import ArchipackPreset, PresetMenuOperator +from .archipack_object import ArchipackCreateTool, ArchipackObject + + +class Fence(): + + def __init__(self): + # total distance from start + self.dist = 0 + self.t_start = 0 + self.t_end = 0 + self.dz = 0 + self.z0 = 0 + self.a0 = 0 + + def set_offset(self, offset, last=None): + """ + Offset line and compute intersection point + between segments + """ + self.line = self.make_offset(offset, last) + + @property + def t_diff(self): + return self.t_end - self.t_start + + def straight_fence(self, a0, length): + s = self.straight(length).rotate(a0) + return StraightFence(s.p, s.v) + + def curved_fence(self, a0, da, radius): + n = self.normal(1).rotate(a0).scale(radius) + if da < 0: + n.v = -n.v + a0 = n.angle + c = n.p - n.v + return CurvedFence(c, radius, a0, da) + + +class StraightFence(Fence, Line): + def __str__(self): + return "t_start:{} t_end:{} dist:{}".format(self.t_start, self.t_end, self.dist) + + def __init__(self, p, v): + Fence.__init__(self) + Line.__init__(self, p, v) + + +class CurvedFence(Fence, Arc): + def __str__(self): + return "t_start:{} t_end:{} dist:{}".format(self.t_start, self.t_end, self.dist) + + def __init__(self, c, radius, a0, da): + Fence.__init__(self) + Arc.__init__(self, c, radius, a0, da) + + +class FenceSegment(): + def __str__(self): + return "t_start:{} t_end:{} n_step:{} t_step:{} i_start:{} i_end:{}".format( + self.t_start, self.t_end, self.n_step, self.t_step, self.i_start, self.i_end) + + def __init__(self, t_start, t_end, n_step, t_step, i_start, i_end): + self.t_start = t_start + self.t_end = t_end + self.n_step = n_step + self.t_step = t_step + self.i_start = i_start + self.i_end = i_end + + +class FenceGenerator(): + + def __init__(self, parts): + self.parts = parts + self.segs = [] + self.length = 0 + self.user_defined_post = None + self.user_defined_uvs = None + self.user_defined_mat = None + + def add_part(self, part): + + if len(self.segs) < 1: + s = None + else: + s = self.segs[-1] + + # start a new fence + if s is None: + if part.type == 'S_FENCE': + p = Vector((0, 0)) + v = part.length * Vector((cos(part.a0), sin(part.a0))) + s = StraightFence(p, v) + elif part.type == 'C_FENCE': + c = -part.radius * Vector((cos(part.a0), sin(part.a0))) + s = CurvedFence(c, part.radius, part.a0, part.da) + else: + if part.type == 'S_FENCE': + s = s.straight_fence(part.a0, part.length) + elif part.type == 'C_FENCE': + s = s.curved_fence(part.a0, part.da, part.radius) + + # s.dist = self.length + # self.length += s.length + self.segs.append(s) + self.last_type = type + + def set_offset(self, offset): + # @TODO: + # re-evaluate length of offset line here + last = None + for seg in self.segs: + seg.set_offset(offset, last) + last = seg.line + + def param_t(self, angle_limit, post_spacing): + """ + setup corners and fences dz + compute index of fences wich belong to each group of fences between corners + compute t of each fence + """ + # segments are group of parts separated by limit angle + self.segments = [] + i_start = 0 + t_start = 0 + dist_0 = 0 + z = 0 + self.length = 0 + n_parts = len(self.parts) - 1 + for i, f in enumerate(self.segs): + f.dist = self.length + self.length += f.line.length + + vz0 = Vector((1, 0)) + angle_z = 0 + for i, f in enumerate(self.segs): + dz = self.parts[i].dz + if f.dist > 0: + f.t_start = f.dist / self.length + else: + f.t_start = 0 + + f.t_end = (f.dist + f.line.length) / self.length + f.z0 = z + f.dz = dz + z += dz + + if i < n_parts: + + vz1 = Vector((self.segs[i + 1].length, self.parts[i + 1].dz)) + angle_z = abs(vz0.angle_signed(vz1)) + vz0 = vz1 + + if (abs(self.parts[i + 1].a0) >= angle_limit or angle_z >= angle_limit): + l_seg = f.dist + f.line.length - dist_0 + t_seg = f.t_end - t_start + n_fences = max(1, int(l_seg / post_spacing)) + t_fence = t_seg / n_fences + segment = FenceSegment(t_start, f.t_end, n_fences, t_fence, i_start, i) + dist_0 = f.dist + f.line.length + t_start = f.t_end + i_start = i + self.segments.append(segment) + + manipulators = self.parts[i].manipulators + p0 = f.line.p0.to_3d() + p1 = f.line.p1.to_3d() + # angle from last to current segment + if i > 0: + v0 = self.segs[i - 1].line.straight(-1, 1).v.to_3d() + v1 = f.line.straight(1, 0).v.to_3d() + manipulators[0].set_pts([p0, v0, v1]) + + if type(f).__name__ == "StraightFence": + # segment length + manipulators[1].type_key = 'SIZE' + manipulators[1].prop1_name = "length" + manipulators[1].set_pts([p0, p1, (1, 0, 0)]) + else: + # segment radius + angle + v0 = (f.line.p0 - f.c).to_3d() + v1 = (f.line.p1 - f.c).to_3d() + manipulators[1].type_key = 'ARC_ANGLE_RADIUS' + manipulators[1].prop1_name = "da" + manipulators[1].prop2_name = "radius" + manipulators[1].set_pts([f.c.to_3d(), v0, v1]) + + # snap manipulator, dont change index ! + manipulators[2].set_pts([p0, p1, (1, 0, 0)]) + + f = self.segs[-1] + l_seg = f.dist + f.line.length - dist_0 + t_seg = f.t_end - t_start + n_fences = max(1, int(l_seg / post_spacing)) + t_fence = t_seg / n_fences + segment = FenceSegment(t_start, f.t_end, n_fences, t_fence, i_start, len(self.segs) - 1) + self.segments.append(segment) + + def setup_user_defined_post(self, o, post_x, post_y, post_z): + self.user_defined_post = o + x = o.bound_box[6][0] - o.bound_box[0][0] + y = o.bound_box[6][1] - o.bound_box[0][1] + z = o.bound_box[6][2] - o.bound_box[0][2] + self.user_defined_post_scale = Vector((post_x / x, post_y / -y, post_z / z)) + m = o.data + # create vertex group lookup dictionary for names + vgroup_names = {vgroup.index: vgroup.name for vgroup in o.vertex_groups} + # create dictionary of vertex group assignments per vertex + self.vertex_groups = [[vgroup_names[g.group] for g in v.groups] for v in m.vertices] + # uvs + uv_act = m.uv_layers.active + if uv_act is not None: + uv_layer = uv_act.data + self.user_defined_uvs = [[uv_layer[li].uv for li in p.loop_indices] for p in m.polygons] + else: + self.user_defined_uvs = [[(0, 0) for i in p.vertices] for p in m.polygons] + # material ids + self.user_defined_mat = [p.material_index for p in m.polygons] + + def get_user_defined_post(self, tM, z0, z1, z2, slope, post_z, verts, faces, matids, uvs): + f = len(verts) + m = self.user_defined_post.data + for i, g in enumerate(self.vertex_groups): + co = m.vertices[i].co.copy() + co.x *= self.user_defined_post_scale.x + co.y *= self.user_defined_post_scale.y + co.z *= self.user_defined_post_scale.z + if 'Slope' in g: + co.z += co.y * slope + verts.append(tM * co) + matids += self.user_defined_mat + faces += [tuple([i + f for i in p.vertices]) for p in m.polygons] + uvs += self.user_defined_uvs + + def get_post(self, post, post_x, post_y, post_z, post_alt, sub_offset_x, + id_mat, verts, faces, matids, uvs): + + n, dz, zl = post + slope = dz * post_y + + if self.user_defined_post is not None: + x, y = -n.v.normalized() + p = n.p + sub_offset_x * n.v.normalized() + tM = Matrix([ + [x, y, 0, p.x], + [y, -x, 0, p.y], + [0, 0, 1, zl + post_alt], + [0, 0, 0, 1] + ]) + self.get_user_defined_post(tM, zl, 0, 0, dz, post_z, verts, faces, matids, uvs) + return + + z3 = zl + post_z + post_alt - slope + z4 = zl + post_z + post_alt + slope + z0 = zl + post_alt - slope + z1 = zl + post_alt + slope + vn = n.v.normalized() + dx = post_x * vn + dy = post_y * Vector((vn.y, -vn.x)) + oy = sub_offset_x * vn + x0, y0 = n.p - dx + dy + oy + x1, y1 = n.p - dx - dy + oy + x2, y2 = n.p + dx - dy + oy + x3, y3 = n.p + dx + dy + oy + f = len(verts) + verts.extend([(x0, y0, z0), (x0, y0, z3), + (x1, y1, z1), (x1, y1, z4), + (x2, y2, z1), (x2, y2, z4), + (x3, y3, z0), (x3, y3, z3)]) + faces.extend([(f, f + 1, f + 3, f + 2), + (f + 2, f + 3, f + 5, f + 4), + (f + 4, f + 5, f + 7, f + 6), + (f + 6, f + 7, f + 1, f), + (f, f + 2, f + 4, f + 6), + (f + 7, f + 5, f + 3, f + 1)]) + matids.extend([id_mat, id_mat, id_mat, id_mat, id_mat, id_mat]) + x = [(0, 0), (0, post_z), (post_x, post_z), (post_x, 0)] + y = [(0, 0), (0, post_z), (post_y, post_z), (post_y, 0)] + z = [(0, 0), (post_x, 0), (post_x, post_y), (0, post_y)] + uvs.extend([x, y, x, y, z, z]) + + def get_panel(self, subs, altitude, panel_x, panel_z, sub_offset_x, idmat, verts, faces, matids, uvs): + n_subs = len(subs) + if n_subs < 1: + return + f = len(verts) + x0 = sub_offset_x - 0.5 * panel_x + x1 = sub_offset_x + 0.5 * panel_x + z0 = 0 + z1 = panel_z + profile = [Vector((x0, z0)), Vector((x1, z0)), Vector((x1, z1)), Vector((x0, z1))] + user_path_uv_v = [] + n_sections = n_subs - 1 + n, dz, zl = subs[0] + p0 = n.p + v0 = n.v.normalized() + for s, section in enumerate(subs): + n, dz, zl = section + p1 = n.p + if s < n_sections: + v1 = subs[s + 1][0].v.normalized() + dir = (v0 + v1).normalized() + scale = 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1)))) + for p in profile: + x, y = n.p + scale * p.x * dir + z = zl + p.y + altitude + verts.append((x, y, z)) + if s > 0: + user_path_uv_v.append((p1 - p0).length) + p0 = p1 + v0 = v1 + + # build faces using Panel + lofter = Lofter( + # closed_shape, index, x, y, idmat + True, + [i for i in range(len(profile))], + [p.x for p in profile], + [p.y for p in profile], + [idmat for i in range(len(profile))], + closed_path=False, + user_path_uv_v=user_path_uv_v, + user_path_verts=n_subs + ) + faces += lofter.faces(16, offset=f, path_type='USER_DEFINED') + matids += lofter.mat(16, idmat, idmat, path_type='USER_DEFINED') + v = Vector((0, 0)) + uvs += lofter.uv(16, v, v, v, v, 0, v, 0, 0, path_type='USER_DEFINED') + + def make_subs(self, x, y, z, post_y, altitude, + sub_spacing, offset_x, sub_offset_x, mat, verts, faces, matids, uvs): + + t_post = (0.5 * post_y - y) / self.length + t_spacing = (sub_spacing + y) / self.length + + for segment in self.segments: + t_step = segment.t_step + t_start = segment.t_start + t_post + s = 0 + s_sub = t_step - 2 * t_post + n_sub = int(s_sub / t_spacing) + if n_sub > 0: + t_sub = s_sub / n_sub + else: + t_sub = 1 + i = segment.i_start + while s < segment.n_step: + t_cur = t_start + s * t_step + for j in range(1, n_sub): + t_s = t_cur + t_sub * j + while self.segs[i].t_end < t_s: + i += 1 + f = self.segs[i] + t = (t_s - f.t_start) / f.t_diff + n = f.line.normal(t) + post = (n, f.dz / f.length, f.z0 + f.dz * t) + self.get_post(post, x, y, z, altitude, sub_offset_x, mat, verts, faces, matids, uvs) + s += 1 + + def make_post(self, x, y, z, altitude, x_offset, mat, verts, faces, matids, uvs): + + for segment in self.segments: + t_step = segment.t_step + t_start = segment.t_start + s = 0 + i = segment.i_start + while s < segment.n_step: + t_cur = t_start + s * t_step + while self.segs[i].t_end < t_cur: + i += 1 + f = self.segs[i] + t = (t_cur - f.t_start) / f.t_diff + n = f.line.normal(t) + post = (n, f.dz / f.line.length, f.z0 + f.dz * t) + # self.get_post(post, x, y, z, altitude, x_offset, mat, verts, faces, matids, uvs) + self.get_post(post, x, y, z, altitude, 0, mat, verts, faces, matids, uvs) + s += 1 + + if segment.i_end + 1 == len(self.segs): + f = self.segs[segment.i_end] + n = f.line.normal(1) + post = (n, f.dz / f.line.length, f.z0 + f.dz) + # self.get_post(post, x, y, z, altitude, x_offset, mat, verts, faces, matids, uvs) + self.get_post(post, x, y, z, altitude, 0, mat, verts, faces, matids, uvs) + + def make_panels(self, x, z, post_y, altitude, panel_dist, + offset_x, sub_offset_x, idmat, verts, faces, matids, uvs): + + t_post = (0.5 * post_y + panel_dist) / self.length + for segment in self.segments: + t_step = segment.t_step + t_start = segment.t_start + s = 0 + i = segment.i_start + while s < segment.n_step: + subs = [] + t_cur = t_start + s * t_step + t_post + t_end = t_start + (s + 1) * t_step - t_post + # find first section + while self.segs[i].t_end < t_cur and i < segment.i_end: + i += 1 + f = self.segs[i] + # 1st section + t = (t_cur - f.t_start) / f.t_diff + n = f.line.normal(t) + subs.append((n, f.dz / f.line.length, f.z0 + f.dz * t)) + # crossing sections -> new segment + while i < segment.i_end: + f = self.segs[i] + if f.t_end < t_end: + if type(f).__name__ == 'CurvedFence': + # cant end after segment + t0 = max(0, (t_cur - f.t_start) / f.t_diff) + t1 = min(1, (t_end - f.t_start) / f.t_diff) + n_s = int(max(1, abs(f.da) * (5) / pi - 1)) + dt = (t1 - t0) / n_s + for j in range(1, n_s + 1): + t = t0 + dt * j + n = f.line.sized_normal(t, 1) + # n.p = f.lerp(x_offset) + subs.append((n, f.dz / f.line.length, f.z0 + f.dz * t)) + else: + n = f.line.normal(1) + subs.append((n, f.dz / f.line.length, f.z0 + f.dz)) + if f.t_end >= t_end: + break + elif f.t_start < t_end: + i += 1 + + f = self.segs[i] + # last section + if type(f).__name__ == 'CurvedFence': + # cant start before segment + t0 = max(0, (t_cur - f.t_start) / f.t_diff) + t1 = min(1, (t_end - f.t_start) / f.t_diff) + n_s = int(max(1, abs(f.da) * (5) / pi - 1)) + dt = (t1 - t0) / n_s + for j in range(1, n_s + 1): + t = t0 + dt * j + n = f.line.sized_normal(t, 1) + # n.p = f.lerp(x_offset) + subs.append((n, f.dz / f.line.length, f.z0 + f.dz * t)) + else: + t = (t_end - f.t_start) / f.t_diff + n = f.line.normal(t) + subs.append((n, f.dz / f.line.length, f.z0 + f.dz * t)) + + # self.get_panel(subs, altitude, x, z, 0, idmat, verts, faces, matids, uvs) + self.get_panel(subs, altitude, x, z, sub_offset_x, idmat, verts, faces, matids, uvs) + s += 1 + + def make_profile(self, profile, idmat, + x_offset, z_offset, extend, verts, faces, matids, uvs): + + last = None + for seg in self.segs: + seg.p_line = seg.make_offset(x_offset, last) + last = seg.p_line + + n_fences = len(self.segs) - 1 + + if n_fences < 0: + return + + sections = [] + + f = self.segs[0] + + # first step + if extend != 0 and f.p_line.length != 0: + t = -extend / self.segs[0].p_line.length + n = f.p_line.sized_normal(t, 1) + # n.p = f.lerp(x_offset) + sections.append((n, f.dz / f.p_line.length, f.z0 + f.dz * t)) + + # add first section + n = f.p_line.sized_normal(0, 1) + # n.p = f.lerp(x_offset) + sections.append((n, f.dz / f.p_line.length, f.z0)) + + for s, f in enumerate(self.segs): + if f.p_line.length == 0: + continue + if type(f).__name__ == 'CurvedFence': + n_s = int(max(1, abs(f.da) * 30 / pi - 1)) + for i in range(1, n_s + 1): + t = i / n_s + n = f.p_line.sized_normal(t, 1) + # n.p = f.lerp(x_offset) + sections.append((n, f.dz / f.p_line.length, f.z0 + f.dz * t)) + else: + n = f.p_line.sized_normal(1, 1) + # n.p = f.lerp(x_offset) + sections.append((n, f.dz / f.p_line.length, f.z0 + f.dz)) + + if extend != 0 and f.p_line.length != 0: + t = 1 + extend / self.segs[-1].p_line.length + n = f.p_line.sized_normal(t, 1) + # n.p = f.lerp(x_offset) + sections.append((n, f.dz / f.p_line.length, f.z0 + f.dz * t)) + + user_path_verts = len(sections) + offset = len(verts) + if user_path_verts > 0: + user_path_uv_v = [] + n, dz, z0 = sections[-1] + sections[-1] = (n, dz, z0) + n_sections = user_path_verts - 1 + n, dz, zl = sections[0] + p0 = n.p + v0 = n.v.normalized() + for s, section in enumerate(sections): + n, dz, zl = section + p1 = n.p + if s < n_sections: + v1 = sections[s + 1][0].v.normalized() + dir = (v0 + v1).normalized() + scale = min(10, 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1))))) + for p in profile: + # x, y = n.p + scale * (x_offset + p.x) * dir + x, y = n.p + scale * p.x * dir + z = zl + p.y + z_offset + verts.append((x, y, z)) + if s > 0: + user_path_uv_v.append((p1 - p0).length) + p0 = p1 + v0 = v1 + + # build faces using Panel + lofter = Lofter( + # closed_shape, index, x, y, idmat + True, + [i for i in range(len(profile))], + [p.x for p in profile], + [p.y for p in profile], + [idmat for i in range(len(profile))], + closed_path=False, + user_path_uv_v=user_path_uv_v, + user_path_verts=user_path_verts + ) + faces += lofter.faces(16, offset=offset, path_type='USER_DEFINED') + matids += lofter.mat(16, idmat, idmat, path_type='USER_DEFINED') + v = Vector((0, 0)) + uvs += lofter.uv(16, v, v, v, v, 0, v, 0, 0, path_type='USER_DEFINED') + + +def update(self, context): + self.update(context) + + +def update_manipulators(self, context): + self.update(context, manipulable_refresh=True) + + +def update_path(self, context): + self.update_path(context) + + +def update_type(self, context): + + d = self.find_datablock_in_selection(context) + + if d is not None and d.auto_update: + + d.auto_update = False + # find part index + idx = 0 + for i, part in enumerate(d.parts): + if part == self: + idx = i + break + part = d.parts[idx] + a0 = 0 + if idx > 0: + g = d.get_generator() + w0 = g.segs[idx - 1] + a0 = w0.straight(1).angle + if "C_" in self.type: + w = w0.straight_fence(part.a0, part.length) + else: + w = w0.curved_fence(part.a0, part.da, part.radius) + else: + g = FenceGenerator(None) + g.add_part(self) + w = g.segs[0] + + # w0 - w - w1 + dp = w.p1 - w.p0 + if "C_" in self.type: + part.radius = 0.5 * dp.length + part.da = pi + a0 = atan2(dp.y, dp.x) - pi / 2 - a0 + else: + part.length = dp.length + a0 = atan2(dp.y, dp.x) - a0 + + if a0 > pi: + a0 -= 2 * pi + if a0 < -pi: + a0 += 2 * pi + part.a0 = a0 + + if idx + 1 < d.n_parts: + # adjust rotation of next part + part1 = d.parts[idx + 1] + if "C_" in part.type: + a0 = part1.a0 - pi / 2 + else: + a0 = part1.a0 + w.straight(1).angle - atan2(dp.y, dp.x) + + if a0 > pi: + a0 -= 2 * pi + if a0 < -pi: + a0 += 2 * pi + part1.a0 = a0 + + d.auto_update = True + + +materials_enum = ( + ('0', 'Wood', '', 0), + ('1', 'Metal', '', 1), + ('2', 'Glass', '', 2) + ) + + +class archipack_fence_material(PropertyGroup): + index = EnumProperty( + items=materials_enum, + default='0', + update=update + ) + + def find_datablock_in_selection(self, context): + """ + find witch selected object this instance belongs to + provide support for "copy to selected" + """ + selected = [o for o in context.selected_objects] + for o in selected: + props = archipack_fence.datablock(o) + if props: + for part in props.rail_mat: + if part == self: + return props + return None + + def update(self, context): + props = self.find_datablock_in_selection(context) + if props is not None: + props.update(context) + + +class archipack_fence_part(PropertyGroup): + type = EnumProperty( + items=( + ('S_FENCE', 'Straight fence', '', 0), + ('C_FENCE', 'Curved fence', '', 1), + ), + default='S_FENCE', + update=update_type + ) + length = FloatProperty( + name="length", + min=0.01, + default=2.0, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + radius = FloatProperty( + name="radius", + min=0.01, + default=0.7, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + da = FloatProperty( + name="total angle", + min=-pi, + max=pi, + default=pi / 2, + subtype='ANGLE', unit='ROTATION', + update=update + ) + a0 = FloatProperty( + name="angle", + min=-2 * pi, + max=2 * pi, + default=0, + subtype='ANGLE', unit='ROTATION', + update=update + ) + dz = FloatProperty( + name="delta z", + default=0, + unit='LENGTH', subtype='DISTANCE' + ) + + manipulators = CollectionProperty(type=archipack_manipulator) + + def find_datablock_in_selection(self, context): + """ + find witch selected object this instance belongs to + provide support for "copy to selected" + """ + selected = [o for o in context.selected_objects] + for o in selected: + props = archipack_fence.datablock(o) + if props is not None: + for part in props.parts: + if part == self: + return props + return None + + def update(self, context, manipulable_refresh=False): + props = self.find_datablock_in_selection(context) + if props is not None: + props.update(context, manipulable_refresh) + + def draw(self, layout, context, index): + box = layout.box() + row = box.row() + row.prop(self, "type", text=str(index + 1)) + if self.type in ['C_FENCE']: + row = box.row() + row.prop(self, "radius") + row = box.row() + row.prop(self, "da") + else: + row = box.row() + row.prop(self, "length") + row = box.row() + row.prop(self, "a0") + + +class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): + + parts = CollectionProperty(type=archipack_fence_part) + user_defined_path = StringProperty( + name="user defined", + update=update_path + ) + user_defined_spline = IntProperty( + name="Spline index", + min=0, + default=0, + update=update_path + ) + user_defined_resolution = IntProperty( + name="resolution", + min=1, + max=128, + default=12, update=update_path + ) + n_parts = IntProperty( + name="parts", + min=1, + default=1, update=update_manipulators + ) + x_offset = FloatProperty( + name="x offset", + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + + radius = FloatProperty( + name="radius", + min=0.01, + default=0.7, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + da = FloatProperty( + name="angle", + min=-pi, + max=pi, + default=pi / 2, + subtype='ANGLE', unit='ROTATION', + update=update + ) + angle_limit = FloatProperty( + name="angle", + min=0, + max=2 * pi, + default=pi / 8, + subtype='ANGLE', unit='ROTATION', + update=update_manipulators + ) + shape = EnumProperty( + items=( + ('RECTANGLE', 'Straight', '', 0), + ('CIRCLE', 'Curved ', '', 1) + ), + default='RECTANGLE', + update=update + ) + post = BoolProperty( + name='enable', + default=True, + update=update + ) + post_spacing = FloatProperty( + name="spacing", + min=0.1, + default=1.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + post_x = FloatProperty( + name="width", + min=0.001, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + post_y = FloatProperty( + name="length", + min=0.001, max=1000, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + post_z = FloatProperty( + name="height", + min=0.001, + default=1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + post_alt = FloatProperty( + name="altitude", + default=0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + user_defined_post_enable = BoolProperty( + name="User", + update=update, + default=True + ) + user_defined_post = StringProperty( + name="user defined", + update=update + ) + idmat_post = EnumProperty( + name="Post", + items=materials_enum, + default='1', + update=update + ) + subs = BoolProperty( + name='enable', + default=False, + update=update + ) + subs_spacing = FloatProperty( + name="spacing", + min=0.05, + default=0.10, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_x = FloatProperty( + name="width", + min=0.001, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_y = FloatProperty( + name="length", + min=0.001, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_z = FloatProperty( + name="height", + min=0.001, + default=1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_alt = FloatProperty( + name="altitude", + default=0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_offset_x = FloatProperty( + name="offset", + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_bottom = EnumProperty( + name="Bottom", + items=( + ('STEP', 'Follow step', '', 0), + ('LINEAR', 'Linear', '', 1), + ), + default='STEP', + update=update + ) + user_defined_subs_enable = BoolProperty( + name="User", + update=update, + default=True + ) + user_defined_subs = StringProperty( + name="user defined", + update=update + ) + idmat_subs = EnumProperty( + name="Subs", + items=materials_enum, + default='1', + update=update + ) + panel = BoolProperty( + name='enable', + default=True, + update=update + ) + panel_alt = FloatProperty( + name="altitude", + default=0.25, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + panel_x = FloatProperty( + name="width", + min=0.001, + default=0.01, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + panel_z = FloatProperty( + name="height", + min=0.001, + default=0.6, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + panel_dist = FloatProperty( + name="space", + min=0.001, + default=0.05, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + panel_offset_x = FloatProperty( + name="offset", + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + idmat_panel = EnumProperty( + name="Panels", + items=materials_enum, + default='2', + update=update + ) + rail = BoolProperty( + name="enable", + update=update, + default=False + ) + rail_n = IntProperty( + name="number", + default=1, + min=0, + max=31, + update=update + ) + rail_x = FloatVectorProperty( + name="width", + default=[ + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05 + ], + size=31, + min=0.001, + precision=2, step=1, + unit='LENGTH', + update=update + ) + rail_z = FloatVectorProperty( + name="height", + default=[ + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05 + ], + size=31, + min=0.001, + precision=2, step=1, + unit='LENGTH', + update=update + ) + rail_offset = FloatVectorProperty( + name="offset", + default=[ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0 + ], + size=31, + precision=2, step=1, + unit='LENGTH', + update=update + ) + rail_alt = FloatVectorProperty( + name="altitude", + default=[ + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 + ], + size=31, + precision=2, step=1, + unit='LENGTH', + update=update + ) + rail_mat = CollectionProperty(type=archipack_fence_material) + + handrail = BoolProperty( + name="enable", + update=update, + default=True + ) + handrail_offset = FloatProperty( + name="offset", + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_alt = FloatProperty( + name="altitude", + default=1.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_extend = FloatProperty( + name="extend", + min=0, + default=0.1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_slice = BoolProperty( + name='slice', + default=True, + update=update + ) + handrail_slice_right = BoolProperty( + name='slice', + default=True, + update=update + ) + handrail_profil = EnumProperty( + name="Profil", + items=( + ('SQUARE', 'Square', '', 0), + ('CIRCLE', 'Circle', '', 1), + ('COMPLEX', 'Circle over square', '', 2) + ), + default='SQUARE', + update=update + ) + handrail_x = FloatProperty( + name="width", + min=0.001, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_y = FloatProperty( + name="height", + min=0.001, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_radius = FloatProperty( + name="radius", + min=0.001, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + idmat_handrail = EnumProperty( + name="Handrail", + items=materials_enum, + default='0', + update=update + ) + + # UI layout related + parts_expand = BoolProperty( + default=False + ) + rail_expand = BoolProperty( + default=False + ) + idmats_expand = BoolProperty( + default=False + ) + handrail_expand = BoolProperty( + default=False + ) + post_expand = BoolProperty( + default=False + ) + panel_expand = BoolProperty( + default=False + ) + subs_expand = BoolProperty( + default=False + ) + + # Flag to prevent mesh update while making bulk changes over variables + # use : + # .auto_update = False + # bulk changes + # .auto_update = True + auto_update = BoolProperty( + options={'SKIP_SAVE'}, + default=True, + update=update_manipulators + ) + + def setup_manipulators(self): + + if len(self.manipulators) == 0: + s = self.manipulators.add() + s.prop1_name = "width" + s = self.manipulators.add() + s.prop1_name = "height" + s.normal = Vector((0, 1, 0)) + + for i in range(self.n_parts): + p = self.parts[i] + n_manips = len(p.manipulators) + if n_manips == 0: + s = p.manipulators.add() + s.type_key = "ANGLE" + s.prop1_name = "a0" + s = p.manipulators.add() + s.type_key = "SIZE" + s.prop1_name = "length" + s = p.manipulators.add() + # s.type_key = 'SNAP_POINT' + s.type_key = 'WALL_SNAP' + s.prop1_name = str(i) + s.prop2_name = 'post_z' + + def update_parts(self): + + # remove rails materials + for i in range(len(self.rail_mat), self.rail_n, -1): + self.rail_mat.remove(i - 1) + + # add rails + for i in range(len(self.rail_mat), self.rail_n): + self.rail_mat.add() + + # remove parts + for i in range(len(self.parts), self.n_parts, -1): + self.parts.remove(i - 1) + + # add parts + for i in range(len(self.parts), self.n_parts): + self.parts.add() + + self.setup_manipulators() + + def interpolate_bezier(self, pts, wM, p0, p1, resolution): + # straight segment, worth testing here + # since this can lower points count by a resolution factor + # use normalized to handle non linear t + if resolution == 0: + pts.append(wM * p0.co.to_3d()) + else: + v = (p1.co - p0.co).normalized() + d1 = (p0.handle_right - p0.co).normalized() + d2 = (p1.co - p1.handle_left).normalized() + if d1 == v and d2 == v: + pts.append(wM * p0.co.to_3d()) + else: + seg = interpolate_bezier(wM * p0.co, + wM * p0.handle_right, + wM * p1.handle_left, + wM * p1.co, + resolution + 1) + for i in range(resolution): + pts.append(seg[i].to_3d()) + + def from_spline(self, context, wM, resolution, spline): + + o = self.find_in_selection(context) + + if o is None: + return + + tM = wM.copy() + tM.row[0].normalize() + tM.row[1].normalize() + tM.row[2].normalize() + pts = [] + if spline.type == 'POLY': + pt = spline.points[0].co + pts = [wM * p.co.to_3d() for p in spline.points] + if spline.use_cyclic_u: + pts.append(pts[0]) + elif spline.type == 'BEZIER': + pt = spline.bezier_points[0].co + points = spline.bezier_points + for i in range(1, len(points)): + p0 = points[i - 1] + p1 = points[i] + self.interpolate_bezier(pts, wM, p0, p1, resolution) + if spline.use_cyclic_u: + p0 = points[-1] + p1 = points[0] + self.interpolate_bezier(pts, wM, p0, p1, resolution) + pts.append(pts[0]) + else: + pts.append(wM * points[-1].co) + auto_update = self.auto_update + self.auto_update = False + + self.n_parts = len(pts) - 1 + self.update_parts() + + p0 = pts.pop(0) + a0 = 0 + for i, p1 in enumerate(pts): + dp = p1 - p0 + da = atan2(dp.y, dp.x) - a0 + if da > pi: + da -= 2 * pi + if da < -pi: + da += 2 * pi + p = self.parts[i] + p.length = dp.to_2d().length + p.dz = dp.z + p.a0 = da + a0 += da + p0 = p1 + + self.auto_update = auto_update + + o.matrix_world = tM * Matrix([ + [1, 0, 0, pt.x], + [0, 1, 0, pt.y], + [0, 0, 1, pt.z], + [0, 0, 0, 1] + ]) + + def update_path(self, context): + path = context.scene.objects.get(self.user_defined_path) + if path is not None and path.type == 'CURVE': + splines = path.data.splines + if len(splines) > self.user_defined_spline: + self.from_spline( + context, + path.matrix_world, + self.user_defined_resolution, + splines[self.user_defined_spline]) + + def get_generator(self): + g = FenceGenerator(self.parts) + for part in self.parts: + # type, radius, da, length + g.add_part(part) + + g.set_offset(self.x_offset) + # param_t(da, part_length) + g.param_t(self.angle_limit, self.post_spacing) + return g + + def update(self, context, manipulable_refresh=False): + + o = self.find_in_selection(context, self.auto_update) + + if o is None: + return + + # clean up manipulators before any data model change + if manipulable_refresh: + self.manipulable_disable(context) + + self.update_parts() + + verts = [] + faces = [] + matids = [] + uvs = [] + + g = self.get_generator() + + # depth at bottom + # self.manipulators[1].set_pts([(0, 0, 0), (0, 0, self.height), (1, 0, 0)]) + + if self.user_defined_post_enable: + # user defined posts + user_def_post = context.scene.objects.get(self.user_defined_post) + if user_def_post is not None and user_def_post.type == 'MESH': + g.setup_user_defined_post(user_def_post, self.post_x, self.post_y, self.post_z) + + if self.post: + g.make_post(0.5 * self.post_x, 0.5 * self.post_y, self.post_z, + self.post_alt, self.x_offset, + int(self.idmat_post), verts, faces, matids, uvs) + + # reset user def posts + g.user_defined_post = None + + # user defined subs + if self.user_defined_subs_enable: + user_def_subs = context.scene.objects.get(self.user_defined_subs) + if user_def_subs is not None and user_def_subs.type == 'MESH': + g.setup_user_defined_post(user_def_subs, self.subs_x, self.subs_y, self.subs_z) + + if self.subs: + g.make_subs(0.5 * self.subs_x, 0.5 * self.subs_y, self.subs_z, + self.post_y, self.subs_alt, self.subs_spacing, + self.x_offset, self.subs_offset_x, int(self.idmat_subs), verts, faces, matids, uvs) + + g.user_defined_post = None + + if self.panel: + g.make_panels(0.5 * self.panel_x, self.panel_z, self.post_y, + self.panel_alt, self.panel_dist, self.x_offset, self.panel_offset_x, + int(self.idmat_panel), verts, faces, matids, uvs) + + if self.rail: + for i in range(self.rail_n): + x = 0.5 * self.rail_x[i] + y = self.rail_z[i] + rail = [Vector((-x, y)), Vector((-x, 0)), Vector((x, 0)), Vector((x, y))] + g.make_profile(rail, int(self.rail_mat[i].index), self.x_offset - self.rail_offset[i], + self.rail_alt[i], 0, verts, faces, matids, uvs) + + if self.handrail_profil == 'COMPLEX': + sx = self.handrail_x + sy = self.handrail_y + handrail = [Vector((sx * x, sy * y)) for x, y in [ + (-0.28, 1.83), (-0.355, 1.77), (-0.415, 1.695), (-0.46, 1.605), (-0.49, 1.51), (-0.5, 1.415), + (-0.49, 1.315), (-0.46, 1.225), (-0.415, 1.135), (-0.355, 1.06), (-0.28, 1.0), (-0.255, 0.925), + (-0.33, 0.855), (-0.5, 0.855), (-0.5, 0.0), (0.5, 0.0), (0.5, 0.855), (0.33, 0.855), (0.255, 0.925), + (0.28, 1.0), (0.355, 1.06), (0.415, 1.135), (0.46, 1.225), (0.49, 1.315), (0.5, 1.415), + (0.49, 1.51), (0.46, 1.605), (0.415, 1.695), (0.355, 1.77), (0.28, 1.83), (0.19, 1.875), + (0.1, 1.905), (0.0, 1.915), (-0.095, 1.905), (-0.19, 1.875)]] + + elif self.handrail_profil == 'SQUARE': + x = 0.5 * self.handrail_x + y = self.handrail_y + handrail = [Vector((-x, y)), Vector((-x, 0)), Vector((x, 0)), Vector((x, y))] + elif self.handrail_profil == 'CIRCLE': + r = self.handrail_radius + handrail = [Vector((r * sin(0.1 * -a * pi), r * (0.5 + cos(0.1 * -a * pi)))) for a in range(0, 20)] + + if self.handrail: + g.make_profile(handrail, int(self.idmat_handrail), self.x_offset - self.handrail_offset, + self.handrail_alt, self.handrail_extend, verts, faces, matids, uvs) + + bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs, weld=True, clean=True) + + # enable manipulators rebuild + if manipulable_refresh: + self.manipulable_refresh = True + + # restore context + self.restore_context(context) + + def manipulable_setup(self, context): + """ + NOTE: + this one assume context.active_object is the instance this + data belongs to, failing to do so will result in wrong + manipulators set on active object + """ + self.manipulable_disable(context) + + o = context.active_object + + self.setup_manipulators() + + for i, part in enumerate(self.parts): + if i >= self.n_parts: + break + + if i > 0: + # start angle + self.manip_stack.append(part.manipulators[0].setup(context, o, part)) + + # length / radius + angle + self.manip_stack.append(part.manipulators[1].setup(context, o, part)) + + # snap point + self.manip_stack.append(part.manipulators[2].setup(context, o, self)) + + for m in self.manipulators: + self.manip_stack.append(m.setup(context, o, self)) + + +class ARCHIPACK_PT_fence(Panel): + bl_idname = "ARCHIPACK_PT_fence" + bl_label = "Fence" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'ArchiPack' + + @classmethod + def poll(cls, context): + return archipack_fence.filter(context.active_object) + + def draw(self, context): + prop = archipack_fence.datablock(context.active_object) + if prop is None: + return + scene = context.scene + layout = self.layout + row = layout.row(align=True) + row.operator('archipack.fence_manipulate', icon='HAND') + box = layout.box() + # box.label(text="Styles") + row = box.row(align=True) + row.operator("archipack.fence_preset_menu", text=bpy.types.ARCHIPACK_OT_fence_preset_menu.bl_label) + row.operator("archipack.fence_preset", text="", icon='ZOOMIN') + row.operator("archipack.fence_preset", text="", icon='ZOOMOUT').remove_active = True + box = layout.box() + row = box.row(align=True) + row.operator("archipack.fence_curve_update", text="", icon='FILE_REFRESH') + row.prop_search(prop, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE') + if prop.user_defined_path is not "": + box.prop(prop, 'user_defined_spline') + box.prop(prop, 'user_defined_resolution') + box.prop(prop, 'angle_limit') + box.prop(prop, 'x_offset') + box = layout.box() + row = box.row() + if prop.parts_expand: + row.prop(prop, 'parts_expand', icon="TRIA_DOWN", icon_only=True, text="Parts", emboss=False) + box.prop(prop, 'n_parts') + for i, part in enumerate(prop.parts): + part.draw(layout, context, i) + else: + row.prop(prop, 'parts_expand', icon="TRIA_RIGHT", icon_only=True, text="Parts", emboss=False) + + box = layout.box() + row = box.row(align=True) + if prop.handrail_expand: + row.prop(prop, 'handrail_expand', icon="TRIA_DOWN", icon_only=True, text="Handrail", emboss=False) + else: + row.prop(prop, 'handrail_expand', icon="TRIA_RIGHT", icon_only=True, text="Handrail", emboss=False) + + row.prop(prop, 'handrail') + + if prop.handrail_expand: + box.prop(prop, 'handrail_alt') + box.prop(prop, 'handrail_offset') + box.prop(prop, 'handrail_extend') + box.prop(prop, 'handrail_profil') + if prop.handrail_profil != 'CIRCLE': + box.prop(prop, 'handrail_x') + box.prop(prop, 'handrail_y') + else: + box.prop(prop, 'handrail_radius') + row = box.row(align=True) + row.prop(prop, 'handrail_slice') + + box = layout.box() + row = box.row(align=True) + if prop.post_expand: + row.prop(prop, 'post_expand', icon="TRIA_DOWN", icon_only=True, text="Post", emboss=False) + else: + row.prop(prop, 'post_expand', icon="TRIA_RIGHT", icon_only=True, text="Post", emboss=False) + row.prop(prop, 'post') + if prop.post_expand: + box.prop(prop, 'post_spacing') + box.prop(prop, 'post_x') + box.prop(prop, 'post_y') + box.prop(prop, 'post_z') + box.prop(prop, 'post_alt') + row = box.row(align=True) + row.prop(prop, 'user_defined_post_enable', text="") + row.prop_search(prop, "user_defined_post", scene, "objects", text="") + + box = layout.box() + row = box.row(align=True) + if prop.subs_expand: + row.prop(prop, 'subs_expand', icon="TRIA_DOWN", icon_only=True, text="Subs", emboss=False) + else: + row.prop(prop, 'subs_expand', icon="TRIA_RIGHT", icon_only=True, text="Subs", emboss=False) + + row.prop(prop, 'subs') + if prop.subs_expand: + box.prop(prop, 'subs_spacing') + box.prop(prop, 'subs_x') + box.prop(prop, 'subs_y') + box.prop(prop, 'subs_z') + box.prop(prop, 'subs_alt') + box.prop(prop, 'subs_offset_x') + row = box.row(align=True) + row.prop(prop, 'user_defined_subs_enable', text="") + row.prop_search(prop, "user_defined_subs", scene, "objects", text="") + + box = layout.box() + row = box.row(align=True) + if prop.panel_expand: + row.prop(prop, 'panel_expand', icon="TRIA_DOWN", icon_only=True, text="Panels", emboss=False) + else: + row.prop(prop, 'panel_expand', icon="TRIA_RIGHT", icon_only=True, text="Panels", emboss=False) + row.prop(prop, 'panel') + if prop.panel_expand: + box.prop(prop, 'panel_dist') + box.prop(prop, 'panel_x') + box.prop(prop, 'panel_z') + box.prop(prop, 'panel_alt') + box.prop(prop, 'panel_offset_x') + + box = layout.box() + row = box.row(align=True) + if prop.rail_expand: + row.prop(prop, 'rail_expand', icon="TRIA_DOWN", icon_only=True, text="Rails", emboss=False) + else: + row.prop(prop, 'rail_expand', icon="TRIA_RIGHT", icon_only=True, text="Rails", emboss=False) + row.prop(prop, 'rail') + if prop.rail_expand: + box.prop(prop, 'rail_n') + for i in range(prop.rail_n): + box = layout.box() + box.label(text="Rail " + str(i + 1)) + box.prop(prop, 'rail_x', index=i) + box.prop(prop, 'rail_z', index=i) + box.prop(prop, 'rail_alt', index=i) + box.prop(prop, 'rail_offset', index=i) + box.prop(prop.rail_mat[i], 'index', text="") + + box = layout.box() + row = box.row() + + if prop.idmats_expand: + row.prop(prop, 'idmats_expand', icon="TRIA_DOWN", icon_only=True, text="Materials", emboss=False) + box.prop(prop, 'idmat_handrail') + box.prop(prop, 'idmat_panel') + box.prop(prop, 'idmat_post') + box.prop(prop, 'idmat_subs') + else: + row.prop(prop, 'idmats_expand', icon="TRIA_RIGHT", icon_only=True, text="Materials", emboss=False) + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_fence(ArchipackCreateTool, Operator): + bl_idname = "archipack.fence" + bl_label = "Fence" + bl_description = "Fence" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + def create(self, context): + m = bpy.data.meshes.new("Fence") + o = bpy.data.objects.new("Fence", m) + d = m.archipack_fence.add() + # make manipulators selectable + d.manipulable_selectable = True + context.scene.objects.link(o) + o.select = True + context.scene.objects.active = o + self.load_preset(d) + self.add_material(o) + return o + + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.location = bpy.context.scene.cursor_location + o.select = True + context.scene.objects.active = o + self.manipulate() + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ + +class ARCHIPACK_OT_fence_curve_update(Operator): + bl_idname = "archipack.fence_curve_update" + bl_label = "Fence curve update" + bl_description = "Update fence data from curve" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_fence.filter(context.active_object) + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def execute(self, context): + if context.mode == "OBJECT": + d = archipack_fence.datablock(context.active_object) + d.update_path(context) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_fence_from_curve(ArchipackCreateTool, Operator): + bl_idname = "archipack.fence_from_curve" + bl_label = "Fence curve" + bl_description = "Create a fence from a curve" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return context.active_object is not None and context.active_object.type == 'CURVE' + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def create(self, context): + o = None + curve = context.active_object + for i, spline in enumerate(curve.data.splines): + bpy.ops.archipack.fence('INVOKE_DEFAULT', auto_manipulate=False) + o = context.active_object + d = archipack_fence.datablock(o) + d.auto_update = False + d.user_defined_spline = i + d.user_defined_path = curve.name + d.auto_update = True + return o + + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + if o is not None: + o.select = True + context.scene.objects.active = o + # self.manipulate() + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + +# ------------------------------------------------------------------ +# Define operator class to manipulate object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_fence_manipulate(Operator): + bl_idname = "archipack.fence_manipulate" + bl_label = "Manipulate" + bl_description = "Manipulate" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_fence.filter(context.active_object) + + def invoke(self, context, event): + d = archipack_fence.datablock(context.active_object) + d.manipulable_invoke(context) + return {'FINISHED'} + + +# ------------------------------------------------------------------ +# Define operator class to load / save presets +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_fence_preset_menu(PresetMenuOperator, Operator): + bl_idname = "archipack.fence_preset_menu" + bl_label = "Fence Styles" + preset_subdir = "archipack_fence" + + +class ARCHIPACK_OT_fence_preset(ArchipackPreset, Operator): + """Add a Fence Preset""" + bl_idname = "archipack.fence_preset" + bl_label = "Add Fence Style" + preset_menu = "ARCHIPACK_OT_fence_preset_menu" + + @property + def blacklist(self): + return ['manipulators', 'n_parts', 'parts', 'user_defined_path', 'user_defined_spline'] + + +def register(): + bpy.utils.register_class(archipack_fence_material) + bpy.utils.register_class(archipack_fence_part) + bpy.utils.register_class(archipack_fence) + Mesh.archipack_fence = CollectionProperty(type=archipack_fence) + bpy.utils.register_class(ARCHIPACK_OT_fence_preset_menu) + bpy.utils.register_class(ARCHIPACK_PT_fence) + bpy.utils.register_class(ARCHIPACK_OT_fence) + bpy.utils.register_class(ARCHIPACK_OT_fence_preset) + bpy.utils.register_class(ARCHIPACK_OT_fence_manipulate) + bpy.utils.register_class(ARCHIPACK_OT_fence_from_curve) + bpy.utils.register_class(ARCHIPACK_OT_fence_curve_update) + + +def unregister(): + bpy.utils.unregister_class(archipack_fence_material) + bpy.utils.unregister_class(archipack_fence_part) + bpy.utils.unregister_class(archipack_fence) + del Mesh.archipack_fence + bpy.utils.unregister_class(ARCHIPACK_OT_fence_preset_menu) + bpy.utils.unregister_class(ARCHIPACK_PT_fence) + bpy.utils.unregister_class(ARCHIPACK_OT_fence) + bpy.utils.unregister_class(ARCHIPACK_OT_fence_preset) + bpy.utils.unregister_class(ARCHIPACK_OT_fence_manipulate) + bpy.utils.unregister_class(ARCHIPACK_OT_fence_from_curve) + bpy.utils.unregister_class(ARCHIPACK_OT_fence_curve_update) diff --git a/archipack/archipack_floor.py b/archipack/archipack_floor.py new file mode 100644 index 000000000..24917c169 --- /dev/null +++ b/archipack/archipack_floor.py @@ -0,0 +1,1190 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Base code inspired by JARCH Vis +# Original Author: Jacob Morris +# Author : Stephen Leger (s-leger) +# ---------------------------------------------------------- + +import bpy +from bpy.types import Operator, PropertyGroup, Mesh, Panel +from bpy.props import ( + BoolProperty, EnumProperty, FloatProperty, + IntProperty, CollectionProperty + ) +from random import uniform, randint +from math import tan, pi, sqrt +from mathutils import Vector +from .bmesh_utils import BmeshEdit as bmed +from .archipack_manipulator import Manipulable +from .archipack_preset import ArchipackPreset, PresetMenuOperator +from .archipack_object import ArchipackCreateTool, ArchipackObject + + +def create_flooring(if_tile, over_width, over_length, b_width, b_length, b_length2, is_length_vary, + length_vary, num_boards, space_l, space_w, spacing, t_width, t_length, is_offset, offset, + is_ran_offset, offset_vary, t_width2, is_width_vary, width_vary, max_boards, is_ran_thickness, + ran_thickness, th, hb_dir): + + # create siding + if if_tile == "1": # Tiles Regular + return tile_regular(over_width, over_length, t_width, t_length, spacing, is_offset, offset, + is_ran_offset, offset_vary, th) + elif if_tile == "2": # Large + Small + return tile_ls(over_width, over_length, t_width, t_length, spacing, th) + elif if_tile == "3": # Large + Many Small + return tile_lms(over_width, over_length, t_width, spacing, th) + elif if_tile == "4": # Hexagonal + return tile_hexagon(over_width, over_length, t_width2, spacing, th) + elif if_tile == "21": # Planks + return wood_regular(over_width, over_length, b_width, b_length, space_l, space_w, + is_length_vary, length_vary, + is_width_vary, width_vary, + is_offset, offset, + is_ran_offset, offset_vary, + max_boards, is_ran_thickness, + ran_thickness, th) + elif if_tile == "22": # Parquet + return wood_parquet(over_width, over_length, b_width, spacing, num_boards, th) + elif if_tile == "23": # Herringbone Parquet + return wood_herringbone(over_width, over_length, b_width, b_length2, spacing, th, hb_dir, True) + elif if_tile == "24": # Herringbone + return wood_herringbone(over_width, over_length, b_width, b_length2, spacing, th, hb_dir, False) + + return [], [] + + +def wood_herringbone(ow, ol, bw, bl, s, th, hb_dir, stepped): + verts = [] + faces = [] + an_45 = 0.5 * sqrt(2) + x, y, z = 0.0, 0.0, th + x_off, y_off = 0.0, 0.0 # used for finding farther forwards points when stepped + ang_s = s * an_45 + s45 = s / an_45 + + # step variables + if stepped: + x_off = an_45 * bw + y_off = an_45 * bw + + wid_off = an_45 * bl # offset from one end of the board to the other inline with width + len_off = an_45 * bl # offset from one end of the board to the other inline with length + w = bw / an_45 # width adjusted for 45 degree rotation + + # figure out starting position + if hb_dir == "1": + y = -wid_off + + elif hb_dir == "2": + x = ow + y = ol + wid_off + + elif hb_dir == "3": + x = -wid_off + y = ol + + elif hb_dir == "4": + x = ow + wid_off + + # loop going forwards + while (hb_dir == "1" and y < ol + wid_off) or (hb_dir == "2" and y > 0 - wid_off) or \ + (hb_dir == "3" and x < ow + wid_off) or (hb_dir == "4" and x > 0 - wid_off): + going_forwards = True + + # loop going right + while (hb_dir == "1" and x < ow) or (hb_dir == "2" and x > 0) or (hb_dir == "3" and y > 0 - y_off) or \ + (hb_dir == "4" and y < ol + y_off): + p = len(verts) + + # add verts + # forwards + verts.append((x, y, z)) + + if hb_dir == "1": + + if stepped and x != 0: + verts.append((x - x_off, y + y_off, z)) + else: + verts.append((x, y + w, z)) + + if going_forwards: + y += wid_off + else: + y -= wid_off + x += len_off + + verts.append((x, y, z)) + if stepped: + verts.append((x - x_off, y + y_off, z)) + x -= x_off - ang_s + if going_forwards: + y += y_off + ang_s + else: + y -= y_off + ang_s + else: + verts.append((x, y + w, z)) + x += s + + # backwards + elif hb_dir == "2": + + if stepped and x != ow: + verts.append((x + x_off, y - y_off, z)) + else: + verts.append((x, y - w, z)) + + if going_forwards: + y -= wid_off + else: + y += wid_off + x -= len_off + + verts.append((x, y, z)) + if stepped: + verts.append((x + x_off, y - y_off, z)) + x += x_off - ang_s + if going_forwards: + y -= y_off + ang_s + else: + y += y_off + ang_s + else: + verts.append((x, y - w, z)) + x -= s + # right + elif hb_dir == "3": + + if stepped and y != ol: + verts.append((x + y_off, y + x_off, z)) + else: + verts.append((x + w, y, z)) + + if going_forwards: + x += wid_off + else: + x -= wid_off + y -= len_off + + verts.append((x, y, z)) + if stepped: + verts.append((x + y_off, y + x_off, z)) + y += x_off - ang_s + if going_forwards: + x += y_off + ang_s + else: + x -= y_off + ang_s + else: + verts.append((x + w, y, z)) + y -= s + # left + else: + + if stepped and y != 0: + verts.append((x - y_off, y - x_off, z)) + else: + verts.append((x - w, y, z)) + + if going_forwards: + x -= wid_off + else: + x += wid_off + y += len_off + + verts.append((x, y, z)) + if stepped: + verts.append((x - y_off, y - x_off, z)) + y -= x_off - ang_s + if going_forwards: + x -= y_off + ang_s + else: + x += y_off + ang_s + else: + verts.append((x - w, y, z)) + y += s + + # faces + faces.append((p, p + 2, p + 3, p + 1)) + + # flip going_right + going_forwards = not going_forwards + x_off *= -1 + + # if not in forwards position, then move back before adjusting values for next row + if not going_forwards: + x_off = abs(x_off) + if hb_dir == "1": + y -= wid_off + if stepped: + y -= y_off + ang_s + elif hb_dir == "2": + y += wid_off + if stepped: + y += y_off + ang_s + elif hb_dir == "3": + x -= wid_off + if stepped: + x -= y_off + ang_s + else: + x += wid_off + if stepped: + x += y_off + ang_s + + # adjust forwards + if hb_dir == "1": + y += w + s45 + x = 0 + elif hb_dir == "2": + y -= w + s45 + x = ow + elif hb_dir == "3": + x += w + s45 + y = ol + else: + x -= w + s45 + y = 0 + + return verts, faces + + +def tile_ls(ow, ol, tw, tl, s, z): + """ + pattern: + _____ + | |_| + |___| + + x and y are axis of big one + """ + + verts = [] + faces = [] + + # big half size + hw = (tw / 2) - (s / 2) + hl = (tl / 2) - (s / 2) + # small half size + hws = (tw / 4) - (s / 2) + hls = (tl / 4) - (s / 2) + + # small, offset from big x,y + xo = 0.75 * tw + yo = 0.25 * tl + + # offset for pattern + rx = 2.5 * tw + ry = 0.5 * tl + + # width and a half of big + ow_x = ow + 0.5 * tw + ol_y = ol + 0.5 * tl + + # start pattern with big one + x = tw + y = -tl + + while y < ol_y: + + while x < ow_x: + + p = len(verts) + + # Large + x0 = max(0, x - hw) + y0 = max(0, y - hl) + x1 = min(ow, x + hw) + y1 = min(ol, y + hl) + if y1 > 0: + if x1 > 0 and x0 < ow and y0 < ol: + + verts.extend([(x0, y1, z), (x1, y1, z), (x1, y0, z), (x0, y0, z)]) + faces.append((p, p + 1, p + 2, p + 3)) + p = len(verts) + + # Small + x0 = x + xo - hws + y0 = y + yo - hls + x1 = min(ow, x + xo + hws) + + if x1 > 0 and x0 < ow and y0 < ol: + + y1 = min(ol, y + yo + hls) + verts.extend([(x0, y1, z), (x1, y1, z), (x1, y0, z), (x0, y0, z)]) + faces.append((p, p + 1, p + 2, p + 3)) + + x += rx + + y += ry + x = x % rx - tw + if x < -tw: + x += rx + + return verts, faces + + +def tile_hexagon(ow, ol, tw, s, z): + verts = [] + faces = [] + offset = False + + w = tw / 2 + y = 0.0 + h = w * tan(pi / 6) + r = sqrt((w * w) + (h * h)) + + while y < ol + tw: + if not offset: + x = 0.0 + else: + x = w + (s / 2) + + while x < ow + tw: + p = len(verts) + + verts.extend([(x + w, y + h, z), (x, y + r, z), (x - w, y + h, z), + (x - w, y - h, z), (x, y - r, z), (x + w, y - h, z)]) + faces.extend([(p, p + 1, p + 2, p + 3), (p + 3, p + 4, p + 5, p)]) + + x += tw + s + + y += r + h + s + offset = not offset + + return verts, faces + + +def tile_lms(ow, ol, tw, s, z): + verts = [] + faces = [] + small = True + + y = 0.0 + ref = (tw - s) / 2 + + while y < ol: + x = 0.0 + large = False + while x < ow: + if small: + x1 = min(x + ref, ow) + y1 = min(y + ref, ol) + p = len(verts) + verts.extend([(x, y1, z), (x, y, z)]) + verts.extend([(x1, y1, z), (x1, y, z)]) + faces.append((p, p + 1, p + 3, p + 2)) + x += ref + else: + if not large: + x1 = min(x + ref, ow) + for i in range(2): + y0 = y + i * (ref + s) + if x < ow and y0 < ol: + y1 = min(y0 + ref, ol) + p = len(verts) + verts.extend([(x, y1, z), (x, y0, z)]) + verts.extend([(x1, y1, z), (x1, y0, z)]) + faces.append((p, p + 1, p + 3, p + 2)) + x += ref + else: + x1 = min(x + tw, ow) + y1 = min(y + tw, ol) + p = len(verts) + verts.extend([(x, y1, z), (x, y, z)]) + verts.extend([(x1, y1, z), (x1, y, z)]) + faces.append((p, p + 1, p + 3, p + 2)) + x += tw + large = not large + x += s + if small: + y += ref + s + else: + y += tw + s + small = not small + + return verts, faces + + +def tile_regular(ow, ol, tw, tl, s, is_offset, offset, is_ran_offset, offset_vary, z): + verts = [] + faces = [] + off = False + o = 1 / (100 / offset) + y = 0.0 + + while y < ol: + + tw2 = 0 + if is_offset: + if is_ran_offset: + v = tw * 0.0049 * offset_vary + tw2 = uniform((tw / 2) - v, (tw / 2) + v) + elif off: + tw2 = o * tw + x = -tw2 + y1 = min(ol, y + tl) + + while x < ow: + p = len(verts) + x0 = max(0, x) + x1 = min(ow, x + tw) + + verts.extend([(x0, y1, z), (x0, y, z), (x1, y, z), (x1, y1, z)]) + faces.append((p, p + 1, p + 2, p + 3)) + x = x1 + s + + y += tl + s + off = not off + + return verts, faces + + +def wood_parquet(ow, ol, bw, s, num_boards, z): + verts = [] + faces = [] + x = 0.0 + + start_orient_length = True + + # figure board length + bl = (bw * num_boards) + (s * (num_boards - 1)) + while x < ow: + + y = 0.0 + + orient_length = start_orient_length + + while y < ol: + + if orient_length: + y0 = y + y1 = min(y + bl, ol) + + for i in range(num_boards): + + bx = x + i * (bw + s) + + if bx < ow and y < ol: + + # make sure board should be placed + x0 = bx + x1 = min(bx + bw, ow) + + p = len(verts) + verts.extend([(x0, y0, z), (x1, y0, z), (x1, y1, z), (x0, y1, z)]) + faces.append((p, p + 1, p + 2, p + 3)) + + else: + x0 = x + x1 = min(x + bl, ow) + + for i in range(num_boards): + + by = y + i * (bw + s) + + if x < ow and by < ol: + y0 = by + y1 = min(by + bw, ol) + p = len(verts) + + verts.extend([(x0, y0, z), (x1, y0, z), (x1, y1, z), (x0, y1, z)]) + faces.append((p, p + 1, p + 2, p + 3)) + + y += bl + s + + orient_length = not orient_length + + start_orient_length = not start_orient_length + + x += bl + s + + return verts, faces + + +def wood_regular(ow, ol, bw, bl, s_l, s_w, + is_length_vary, length_vary, + is_width_vary, width_vary, + is_offset, offset, + is_ran_offset, offset_vary, + max_boards, is_r_h, + r_h, th): + verts = [] + faces = [] + x = 0.0 + row = 0 + while x < ow: + + if is_width_vary: + v = bw * (width_vary / 100) * 0.499 + bw2 = uniform(bw / 2 - v, bw / 2 + v) + else: + bw2 = bw + + x1 = min(x + bw2, ow) + if is_offset: + if is_ran_offset: + v = bl * (offset_vary / 100) * 0.5 + y = -uniform(bl / 2 - v, bl / 2 + v) + else: + y = -(row % 2) * bl * (offset / 100) + else: + y = 0 + + row += 1 + counter = 1 + + while y < ol: + + z = th + + if is_r_h: + v = z * 0.5 * (r_h / 100) + z = uniform(z / 2 - v, z / 2 + v) + + bl2 = bl + + if is_length_vary: + if (counter >= max_boards): + bl2 = ol + else: + v = bl * (length_vary / 100) * 0.5 + bl2 = uniform(bl / 2 - v, bl / 2 + v) + + y0 = max(0, y) + y1 = min(y + bl2, ol) + + if y1 > y0: + p = len(verts) + + verts.extend([(x, y0, z), (x1, y0, z), (x1, y1, z), (x, y1, z)]) + faces.append((p, p + 1, p + 2, p + 3)) + + y += bl2 + s_l + + counter += 1 + + x += bw2 + s_w + + return verts, faces + + +def tile_grout(ow, ol, depth, th): + z = min(th - 0.001, max(0.001, th - depth)) + x = ow + y = ol + + verts = [(0.0, 0.0, 0.0), (0.0, 0.0, z), (x, 0.0, z), (x, 0.0, 0.0), + (0.0, y, 0.0), (0.0, y, z), (x, y, z), (x, y, 0.0)] + + faces = [(0, 3, 2, 1), (4, 5, 6, 7), (0, 1, 5, 4), + (1, 2, 6, 5), (3, 7, 6, 2), (0, 4, 7, 3)] + + return verts, faces + + +def update(self, context): + self.update(context) + + +class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): + tile_types = EnumProperty( + items=( + ("1", "Tiles", ""), + ("2", "Large + Small", ""), + ("3", "Large + Many Small", ""), + ("4", "Hexagonal", ""), + ("21", "Planks", ""), + ("22", "Parquet", ""), + ("23", "Herringbone Parquet", ""), + ("24", "Herringbone", "") + ), + default="1", + description="Tile Type", + update=update, + name="") + b_length_s = FloatProperty( + name="Board Length", + min=0.01, + default=2.0, + unit='LENGTH', subtype='DISTANCE', + description="Board Length", + update=update) + hb_direction = EnumProperty( + items=( + ("1", "Forwards (+y)", ""), + ("2", "Backwards (-y)", ""), + ("3", "Right (+x)", ""), + ("4", "Left (-x)", "") + ), + name="Direction", + description="Herringbone Direction", + update=update) + thickness = FloatProperty( + name="Floor Thickness", + min=0.01, + default=0.1, + unit='LENGTH', subtype='DISTANCE', + description="Thickness Of Flooring", + update=update) + num_boards = IntProperty( + name="# Of Boards", + min=2, + max=6, + default=4, + description="Number Of Boards In Square", + update=update) + space_l = FloatProperty( + name="Length Spacing", + min=0.001, + default=0.005, + step=0.01, + unit='LENGTH', subtype='DISTANCE', + description="Space Between Boards Length Ways", + update=update) + space_w = FloatProperty( + name="Width Spacing", + min=0.001, + default=0.005, + step=0.01, + unit='LENGTH', subtype='DISTANCE', + description="Space Between Boards Width Ways", + update=update) + spacing = FloatProperty( + name="Spacing", + min=0.001, + default=0.005, + step=0.01, + unit='LENGTH', subtype='DISTANCE', + description="Space Between Tiles/Boards", + update=update) + is_bevel = BoolProperty( + name="Bevel?", + default=False, + update=update) + bevel_res = IntProperty( + name="Bevel Resolution", + min=1, + max=10, + default=1, + update=update) + bevel_amo = FloatProperty( + name="Bevel Amount", + min=0.001, + default=0.0015, + step=0.01, + unit='LENGTH', subtype='DISTANCE', + description="Bevel Amount", + update=update) + is_ran_thickness = BoolProperty( + name="Random Thickness?", + default=False, + update=update) + ran_thickness = FloatProperty( + name="Thickness Variance", + min=0.1, + max=100.0, + default=50.0, + subtype="PERCENTAGE", + update=update) + is_floor_bottom = BoolProperty( + name="Floor bottom", + default=True, + update=update) + t_width = FloatProperty( + name="Tile Width", + min=0.01, + default=0.3, + unit='LENGTH', subtype='DISTANCE', + description="Tile Width", + update=update) + t_length = FloatProperty( + name="Tile Length", + min=0.01, + default=0.3, + unit='LENGTH', subtype='DISTANCE', + description="Tile Length", + update=update) + is_grout = BoolProperty( + name="Grout", + default=False, + description="Enable grout", + update=update) + grout_depth = FloatProperty( + name="Grout Depth", + min=0.001, + default=0.005, + step=0.01, + unit='LENGTH', subtype='DISTANCE', + description="Grout Depth", + update=update) + is_offset = BoolProperty( + name="Offset ?", + default=False, + description="Offset Rows", + update=update) + offset = FloatProperty( + name="Offset", + min=0.001, + max=100.0, + default=50.0, + subtype="PERCENTAGE", + description="Tile Offset Amount", + update=update) + is_random_offset = BoolProperty( + name="Random Offset?", + default=False, + description="Offset Tile Rows Randomly", + update=update) + offset_vary = FloatProperty( + name="Offset Variance", + min=0.001, + max=100.0, + default=50.0, + subtype="PERCENTAGE", + description="Offset Variance", + update=update) + t_width_s = FloatProperty( + name="Small Tile Width", + min=0.02, + default=0.2, + unit='LENGTH', subtype='DISTANCE', + description="Small Tile Width", + update=update) + over_width = FloatProperty( + name="Overall Width", + min=0.02, + default=4, + unit='LENGTH', subtype='DISTANCE', + description="Overall Width", + update=update) + over_length = FloatProperty( + name="Overall Length", + min=0.02, + default=4, + unit='LENGTH', subtype='DISTANCE', + description="Overall Length", + update=update) + b_width = FloatProperty( + name="Board Width", + min=0.01, + default=0.2, + unit='LENGTH', subtype='DISTANCE', + description="Board Width", + update=update) + b_length = FloatProperty( + name="Board Length", + min=0.01, + default=0.8, + unit='LENGTH', subtype='DISTANCE', + description="Board Length", + update=update) + is_length_vary = BoolProperty( + name="Vary Length?", + default=False, + description="Vary Lengths?", + update=update) + length_vary = FloatProperty( + name="Length Variance", + min=1.00, + max=100.0, + default=50.0, + subtype="PERCENTAGE", + description="Length Variance", + update=update) + max_boards = IntProperty( + name="Max # Of Boards", + min=2, + default=2, + description="Maximum Number Of Boards Possible In One Length", + update=update) + is_width_vary = BoolProperty( + name="Vary Width?", + default=False, + description="Vary Widths?", + update=update) + width_vary = FloatProperty( + name="Width Variance", + min=1.00, + max=100.0, + default=50.0, + subtype="PERCENTAGE", + description="Width Variance", + update=update) + is_mat_vary = BoolProperty( + name="Vary Material?", + default=False, + description="Vary Material indexes", + update=update) + mat_vary = IntProperty( + name="#variations", + min=1, + max=10, + default=1, + description="Material index maxi", + update=update) + auto_update = BoolProperty( + options={'SKIP_SAVE'}, + default=True, + update=update + ) + + def setup_manipulators(self): + if len(self.manipulators) < 1: + # add manipulator for x property + s = self.manipulators.add() + s.prop1_name = "over_width" + # s.prop2_name = "x" + s.type_key = 'SIZE' + + # add manipulator for y property + s = self.manipulators.add() + s.prop1_name = "over_length" + # s.prop2_name = "y" + s.type_key = 'SIZE' + + def update(self, context): + + o = self.find_in_selection(context, self.auto_update) + + if o is None: + return + + self.setup_manipulators() + + verts, faces = create_flooring(self.tile_types, self.over_width, + self.over_length, self.b_width, self.b_length, self.b_length_s, + self.is_length_vary, self.length_vary, self.num_boards, self.space_l, + self.space_w, self.spacing, self.t_width, self.t_length, self.is_offset, + self.offset, self.is_random_offset, self.offset_vary, self.t_width_s, + self.is_width_vary, self.width_vary, self.max_boards, self.is_ran_thickness, + self.ran_thickness, self.thickness, self.hb_direction) + + if self.is_mat_vary: + # hexagon made of 2 faces + if self.tile_types == '4': + matids = [] + for i in range(int(len(faces) / 2)): + id = randint(1, self.mat_vary) + matids.extend([id, id]) + else: + matids = [randint(1, self.mat_vary) for i in faces] + else: + matids = [1 for i in faces] + + uvs = [[(0, 0), (0, 1), (1, 1), (1, 0)] for i in faces] + + bmed.buildmesh(context, + o, + verts, + faces, + matids=matids, + uvs=uvs, + weld=False, + auto_smooth=False) + + # cut hexa and herringbone wood + # disable when boolean modifier is found + enable_bissect = True + for m in o.modifiers: + if m.type == 'BOOLEAN': + enable_bissect = False + + if enable_bissect and self.tile_types in ('4', '23', '24'): + bmed.bissect(context, o, Vector((0, 0, 0)), Vector((0, -1, 0))) + # Up + bmed.bissect(context, o, Vector((0, self.over_length, 0)), Vector((0, 1, 0))) + # left + bmed.bissect(context, o, Vector((0, 0, 0)), Vector((-1, 0, 0))) + # right + bmed.bissect(context, o, Vector((self.over_width, 0, 0)), Vector((1, 0, 0))) + + if self.is_bevel: + bevel = self.bevel_amo + else: + bevel = 0 + + if self.is_grout: + th = min(self.grout_depth + bevel, self.thickness - 0.001) + bottom = th + else: + th = self.thickness + bottom = 0 + + bmed.solidify(context, + o, + th, + floor_bottom=( + self.is_floor_bottom and + self.is_ran_thickness and + self.tile_types in ('21') + ), + altitude=bottom) + + # bevel mesh + if self.is_bevel: + bmed.bevel(context, o, self.bevel_amo, segments=self.bevel_res) + + # create grout + if self.is_grout: + verts, faces = tile_grout(self.over_width, self.over_length, self.grout_depth, self.thickness) + matids = [0 for i in faces] + uvs = [[(0, 0), (0, 1), (1, 1), (1, 0)] for i in faces] + bmed.addmesh(context, + o, + verts, + faces, + matids=matids, + uvs=uvs, + weld=False, + auto_smooth=False) + + x, y = self.over_width, self.over_length + self.manipulators[0].set_pts([(0, 0, 0), (x, 0, 0), (1, 0, 0)]) + self.manipulators[1].set_pts([(0, 0, 0), (0, y, 0), (-1, 0, 0)]) + + self.restore_context(context) + + +class ARCHIPACK_PT_floor(Panel): + bl_idname = "ARCHIPACK_PT_floor" + bl_label = "Flooring" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "Archipack" + + @classmethod + def poll(cls, context): + # ensure your object panel only show when active object is the right one + return archipack_floor.filter(context.active_object) + + def draw(self, context): + o = context.active_object + if not archipack_floor.filter(o): + return + layout = self.layout + + # retrieve datablock of your object + props = archipack_floor.datablock(o) + + # Manipulate mode operator + layout.operator('archipack.floor_manipulate', icon='HAND') + + box = layout.box() + row = box.row(align=True) + + # Presets operators + row.operator("archipack.floor_preset_menu", + text=bpy.types.ARCHIPACK_OT_floor_preset_menu.bl_label) + row.operator("archipack.floor_preset", + text="", + icon='ZOOMIN') + row.operator("archipack.floor_preset", + text="", + icon='ZOOMOUT').remove_active = True + + layout.prop(props, "tile_types", icon="OBJECT_DATA") + + layout.separator() + + layout.prop(props, "over_width") + layout.prop(props, "over_length") + layout.separator() + + # width and lengths + layout.prop(props, "thickness") + + type = int(props.tile_types) + + if type > 20: + # Wood types + layout.prop(props, "b_width") + else: + # Tiles types + if type != 4: + # Not hexagonal + layout.prop(props, "t_width") + layout.prop(props, "t_length") + else: + layout.prop(props, "t_width_s") + + # Herringbone + if type in (23, 24): + layout.prop(props, "b_length_s") + layout.prop(props, "hb_direction") + + # Parquet + if type == 22: + layout.prop(props, "num_boards") + + # Planks + if type == 21: + layout.prop(props, "b_length") + layout.prop(props, "space_w") + layout.prop(props, "space_l") + + layout.separator() + layout.prop(props, "is_length_vary", icon="NLA") + if props.is_length_vary: + layout.prop(props, "length_vary") + layout.prop(props, "max_boards") + + layout.separator() + layout.prop(props, "is_width_vary", icon="UV_ISLANDSEL") + if props.is_width_vary: + layout.prop(props, "width_vary") + + layout.separator() + layout.prop(props, "is_ran_thickness", icon="RNDCURVE") + if props.is_ran_thickness: + layout.prop(props, "ran_thickness") + layout.prop(props, "is_floor_bottom", icon="MOVE_DOWN_VEC") + else: + layout.prop(props, "spacing") + + # Planks and tiles + if type in (1, 21): + layout.separator() + layout.prop(props, "is_offset", icon="OOPS") + if props.is_offset: + layout.prop(props, "is_random_offset", icon="NLA") + if not props.is_random_offset: + layout.prop(props, "offset") + else: + layout.prop(props, "offset_vary") + + # bevel + layout.separator() + layout.prop(props, "is_bevel", icon="MOD_BEVEL") + if props.is_bevel: + layout.prop(props, "bevel_res", icon="OUTLINER_DATA_CURVE") + layout.prop(props, "bevel_amo") + + # Grout + layout.separator() + layout.prop(props, "is_grout", icon="OBJECT_DATA") + if props.is_grout: + layout.prop(props, "grout_depth") + + layout.separator() + layout.prop(props, "is_mat_vary", icon="MATERIAL") + if props.is_mat_vary: + layout.prop(props, "mat_vary") + + +class ARCHIPACK_OT_floor(ArchipackCreateTool, Operator): + bl_idname = "archipack.floor" + bl_label = "Floor" + bl_description = "Create Floor" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + def create(self, context): + + # Create an empty mesh datablock + m = bpy.data.meshes.new("Floor") + + # Create an object using the mesh datablock + o = bpy.data.objects.new("Floor", m) + + # Add your properties on mesh datablock + d = m.archipack_floor.add() + + # Link object into scene + context.scene.objects.link(o) + + # select and make active + o.select = True + context.scene.objects.active = o + + # Load preset into datablock + self.load_preset(d) + + # add a material + self.add_material(o) + return o + + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.location = bpy.context.scene.cursor_location + o.select = True + context.scene.objects.active = o + + # Start manipulate mode + self.manipulate() + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_floor_preset_menu(PresetMenuOperator, Operator): + bl_idname = "archipack.floor_preset_menu" + bl_label = "Floor preset" + preset_subdir = "archipack_floor" + + +class ARCHIPACK_OT_floor_preset(ArchipackPreset, Operator): + """Add a Floor Preset""" + bl_idname = "archipack.floor_preset" + bl_label = "Add Floor preset" + preset_menu = "ARCHIPACK_OT_floor_preset_menu" + + @property + def blacklist(self): + return ['manipulators', 'over_length', 'over_width'] + + +class ARCHIPACK_OT_floor_manipulate(Operator): + bl_idname = "archipack.floor_manipulate" + bl_label = "Manipulate" + bl_description = "Manipulate" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_floor.filter(context.active_object) + + def invoke(self, context, event): + d = archipack_floor.datablock(context.active_object) + d.manipulable_invoke(context) + return {'FINISHED'} + + +def register(): + bpy.utils.register_class(archipack_floor) + Mesh.archipack_floor = CollectionProperty(type=archipack_floor) + bpy.utils.register_class(ARCHIPACK_PT_floor) + bpy.utils.register_class(ARCHIPACK_OT_floor) + bpy.utils.register_class(ARCHIPACK_OT_floor_preset_menu) + bpy.utils.register_class(ARCHIPACK_OT_floor_preset) + bpy.utils.register_class(ARCHIPACK_OT_floor_manipulate) + + +def unregister(): + bpy.utils.unregister_class(archipack_floor) + del Mesh.archipack_floor + bpy.utils.unregister_class(ARCHIPACK_PT_floor) + bpy.utils.unregister_class(ARCHIPACK_OT_floor) + bpy.utils.unregister_class(ARCHIPACK_OT_floor_preset_menu) + bpy.utils.unregister_class(ARCHIPACK_OT_floor_preset) + bpy.utils.unregister_class(ARCHIPACK_OT_floor_manipulate) diff --git a/archipack/archipack_gl.py b/archipack/archipack_gl.py new file mode 100644 index 000000000..fc1f8c03f --- /dev/null +++ b/archipack/archipack_gl.py @@ -0,0 +1,1228 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- + +import bgl +import blf +import bpy +from math import sin, cos, atan2, pi +from mathutils import Vector, Matrix +from bpy_extras import view3d_utils, object_utils + + +# ------------------------------------------------------------------ +# Define Gl Handle types +# ------------------------------------------------------------------ + + +class DefaultColorScheme: + """ + Font sizes and basic colour scheme + default to this when not found in addon prefs + Colors are FloatVectorProperty of size 4 and type COLOR_GAMMA + """ + feedback_size_main = 16 + feedback_size_title = 14 + feedback_size_shortcut = 11 + feedback_colour_main = (0.95, 0.95, 0.95, 1.0) + feedback_colour_key = (0.67, 0.67, 0.67, 1.0) + feedback_colour_shortcut = (0.51, 0.51, 0.51, 1.0) + feedback_shortcut_area = (0, 0.4, 0.6, 0.2) + feedback_title_area = (0, 0.4, 0.6, 0.5) + + +""" + # Addon prefs template + + feedback_size_main = IntProperty( + name="Main", + description="Main title font size (pixels)", + min=2, + default=16 + ) + feedback_size_title = IntProperty( + name="Title", + description="Tool name font size (pixels)", + min=2, + default=14 + ) + feedback_size_shortcut = IntProperty( + name="Shortcut", + description="Shortcuts font size (pixels)", + min=2, + default=11 + ) + feedback_shortcut_area = FloatVectorProperty( + name="Background Shortcut", + description="Shortcut area background color", + subtype='COLOR_GAMMA', + default=(0, 0.4, 0.6, 0.2), + size=4, + min=0, max=1 + ) + feedback_title_area = FloatVectorProperty( + name="Background Main", + description="Title area background color", + subtype='COLOR_GAMMA', + default=(0, 0.4, 0.6, 0.5), + size=4, + min=0, max=1 + ) + feedback_colour_main = FloatVectorProperty( + name="Font Main", + description="Title color", + subtype='COLOR_GAMMA', + default=(0.95, 0.95, 0.95, 1.0), + size=4, + min=0, max=1 + ) + feedback_colour_key = FloatVectorProperty( + name="Font Shortcut key", + description="KEY label color", + subtype='COLOR_GAMMA', + default=(0.67, 0.67, 0.67, 1.0), + size=4, + min=0, max=1 + ) + feedback_colour_shortcut = FloatVectorProperty( + name="Font Shortcut hint", + description="Shortcuts text color", + subtype='COLOR_GAMMA', + default=(0.51, 0.51, 0.51, 1.0), + size=4, + min=0, max=1 + ) + + def draw(self, context): + layout = self.layout + box = layout.box() + row = box.row() + split = row.split(percentage=0.5) + col = split.column() + col.label(text="Colors:") + row = col.row(align=True) + row.prop(self, "feedback_title_area") + row = col.row(align=True) + row.prop(self, "feedback_shortcut_area") + row = col.row(align=True) + row.prop(self, "feedback_colour_main") + row = col.row(align=True) + row.prop(self, "feedback_colour_key") + row = col.row(align=True) + row.prop(self, "feedback_colour_shortcut") + col = split.column() + col.label(text="Font size:") + col.prop(self, "feedback_size_main") + col.prop(self, "feedback_size_title") + col.prop(self, "feedback_size_shortcut") +""" + + +# @TODO: +# 1 Make a clear separation of 2d (pixel position) and 3d (world position) +# modes way to set gl coords +# 2 Unify methods to set points - currently set_pts, set_pos ... +# 3 Put all Gl part in a sub module as it may be used by other devs +# as gl toolkit abstraction for screen feedback +# 4 Implement cursor badges (np_station sample) +# 5 Define a clear color scheme so it is easy to customize +# 6 Allow different arguments for each classes like +# eg: for line p0 p1, p0 and vector (p1-p0) +# raising exceptions when incomplete +# 7 Use correct words, normal is not realy a normal +# but a perpendicular +# May be hard code more shapes ? +# Fine tuned text styles with shadows and surronding boxes / backgrounds +# Extending tests to hdr screens, ultra wide ones and so on +# Circular handle, handle styling (only border, filling ...) + +# Keep point 3 in mind while doing this, to keep it simple and easy to use +# Take inspiration from other's feed back systems, talk to other devs +# and find who actually work on bgl future for 2.8 release + + +class Gl(): + """ + handle 3d -> 2d gl drawing + d : dimensions + 3 to convert pos from 3d + 2 to keep pos as 2d absolute screen position + """ + def __init__(self, + d=3, + colour=(0.0, 0.0, 0.0, 1.0)): + # nth dimensions of input coords 3=word coords 2=pixel screen coords + self.d = d + self.pos_2d = Vector((0, 0)) + self.colour_inactive = colour + + @property + def colour(self): + return self.colour_inactive + + def position_2d_from_coord(self, context, coord, render=False): + """ coord given in local input coordsys + """ + if self.d == 2: + return coord + if render: + return self.get_render_location(context, coord) + region = context.region + rv3d = context.region_data + loc = view3d_utils.location_3d_to_region_2d(region, rv3d, coord, self.pos_2d) + return loc + + def get_render_location(self, context, coord): + scene = context.scene + co_2d = object_utils.world_to_camera_view(scene, scene.camera, coord) + # Get pixel coords + render_scale = scene.render.resolution_percentage / 100 + render_size = (int(scene.render.resolution_x * render_scale), + int(scene.render.resolution_y * render_scale)) + return [round(co_2d.x * render_size[0]), round(co_2d.y * render_size[1])] + + def _end(self): + bgl.glEnd() + bgl.glPopAttrib() + bgl.glLineWidth(1) + bgl.glDisable(bgl.GL_BLEND) + bgl.glColor4f(0.0, 0.0, 0.0, 1.0) + + +class GlText(Gl): + + def __init__(self, + d=3, + label="", + value=None, + precision=2, + unit_mode='AUTO', + unit_type='SIZE', + dimension=1, + angle=0, + font_size=12, + colour=(1, 1, 1, 1), + z_axis=Vector((0, 0, 1))): + """ + d: [2|3] coords type: 2 for coords in screen pixels, 3 for 3d world location + label : string label + value : float value (will add unit according following settings) + precision : integer rounding for values + dimension : [1 - 3] nth dimension of unit (single, square, cubic) + unit_mode : ['AUTO','METER','CENTIMETER','MILIMETER','FEET','INCH','RADIANS','DEGREE'] + unit type to use to postfix values + auto use scene units setup + unit_type : ['SIZE','ANGLE'] + unit type to add to value + angle : angle to rotate text + + """ + self.z_axis = z_axis + # text, add as prefix to value + self.label = label + # value with unit related + self.value = value + self.precision = precision + self.dimension = dimension + self.unit_type = unit_type + self.unit_mode = unit_mode + + self.font_size = font_size + self.angle = angle + Gl.__init__(self, d) + self.colour_inactive = colour + # store text with units + self._text = "" + + def text_size(self, context): + """ + overall on-screen size in pixels + """ + dpi, font_id = context.user_preferences.system.dpi, 0 + if self.angle != 0: + blf.enable(font_id, blf.ROTATION) + blf.rotation(font_id, self.angle) + blf.aspect(font_id, 1.0) + blf.size(font_id, self.font_size, dpi) + x, y = blf.dimensions(font_id, self.text) + if self.angle != 0: + blf.disable(font_id, blf.ROTATION) + return Vector((x, y)) + + @property + def pts(self): + return [self.pos_3d] + + @property + def text(self): + s = self.label + self._text + return s.strip() + + def add_units(self, context): + if self.value is None: + return "" + if self.unit_type == 'ANGLE': + scale = 1 + else: + scale = context.scene.unit_settings.scale_length + + val = self.value * scale + mode = self.unit_mode + if mode == 'AUTO': + if self.unit_type == 'ANGLE': + mode = context.scene.unit_settings.system_rotation + else: + if context.scene.unit_settings.system == "IMPERIAL": + if round(val * (3.2808399 ** self.dimension), 2) >= 1.0: + mode = 'FEET' + else: + mode = 'INCH' + elif context.scene.unit_settings.system == "METRIC": + if round(val, 2) >= 1.0: + mode = 'METER' + else: + if round(val, 2) >= 0.01: + mode = 'CENTIMETER' + else: + mode = 'MILIMETER' + # convert values + if mode == 'METER': + unit = "m" + elif mode == 'CENTIMETER': + val *= (100 ** self.dimension) + unit = "cm" + elif mode == 'MILIMETER': + val *= (1000 ** self.dimension) + unit = 'mm' + elif mode == 'INCH': + val *= (39.3700787 ** self.dimension) + unit = "in" + elif mode == 'FEET': + val *= (3.2808399 ** self.dimension) + unit = "ft" + elif mode == 'RADIANS': + unit = "" + elif mode == 'DEGREES': + val = self.value / pi * 180 + unit = "°" + else: + unit = "" + if self.dimension == 2: + unit += "\u00b2" # Superscript two + elif self.dimension == 3: + unit += "\u00b3" # Superscript three + + fmt = "%1." + str(self.precision) + "f " + unit + return fmt % val + + def set_pos(self, context, value, pos_3d, direction, angle=0, normal=Vector((0, 0, 1))): + self.up_axis = direction.normalized() + self.c_axis = self.up_axis.cross(normal) + self.pos_3d = pos_3d + self.value = value + self.angle = angle + self._text = self.add_units(context) + + def draw(self, context, render=False): + self.render = render + x, y = self.position_2d_from_coord(context, self.pts[0], render) + # dirty fast assignment + dpi, font_id = context.user_preferences.system.dpi, 0 + bgl.glColor4f(*self.colour) + if self.angle != 0: + blf.enable(font_id, blf.ROTATION) + blf.rotation(font_id, self.angle) + blf.size(font_id, self.font_size, dpi) + blf.position(font_id, x, y, 0) + blf.draw(font_id, self.text) + if self.angle != 0: + blf.disable(font_id, blf.ROTATION) + + +class GlBaseLine(Gl): + + def __init__(self, + d=3, + width=1, + style=bgl.GL_LINE, + closed=False): + Gl.__init__(self, d) + # default line width + self.width = width + # default line style + self.style = style + # allow closed lines + self.closed = False + + def draw(self, context, render=False): + """ + render flag when rendering + """ + bgl.glPushAttrib(bgl.GL_ENABLE_BIT) + if self.style == bgl.GL_LINE_STIPPLE: + bgl.glLineStipple(1, 0x9999) + bgl.glEnable(self.style) + bgl.glEnable(bgl.GL_BLEND) + if render: + # enable anti-alias on lines + bgl.glEnable(bgl.GL_LINE_SMOOTH) + bgl.glColor4f(*self.colour) + bgl.glLineWidth(self.width) + if self.closed: + bgl.glBegin(bgl.GL_LINE_LOOP) + else: + bgl.glBegin(bgl.GL_LINE_STRIP) + + for pt in self.pts: + x, y = self.position_2d_from_coord(context, pt, render) + bgl.glVertex2f(x, y) + self._end() + + +class GlLine(GlBaseLine): + """ + 2d/3d Line + """ + def __init__(self, d=3, p=None, v=None, p0=None, p1=None, z_axis=None): + """ + d=3 use 3d coords, d=2 use 2d pixels coords + Init by either + p: Vector or tuple origin + v: Vector or tuple size and direction + or + p0: Vector or tuple 1 point location + p1: Vector or tuple 2 point location + Will convert any into Vector 3d + both optionnals + """ + if p is not None and v is not None: + self.p = Vector(p) + self.v = Vector(v) + elif p0 is not None and p1 is not None: + self.p = Vector(p0) + self.v = Vector(p1) - self.p + else: + self.p = Vector((0, 0, 0)) + self.v = Vector((0, 0, 0)) + if z_axis is not None: + self.z_axis = z_axis + else: + self.z_axis = Vector((0, 0, 1)) + GlBaseLine.__init__(self, d) + + @property + def p0(self): + return self.p + + @property + def p1(self): + return self.p + self.v + + @p0.setter + def p0(self, p0): + """ + Note: setting p0 + move p0 only + """ + p1 = self.p1 + self.p = Vector(p0) + self.v = p1 - p0 + + @p1.setter + def p1(self, p1): + """ + Note: setting p1 + move p1 only + """ + self.v = Vector(p1) - self.p + + @property + def length(self): + return self.v.length + + @property + def angle(self): + return atan2(self.v.y, self.v.x) + + @property + def cross(self): + """ + Vector perpendicular on plane defined by z_axis + lie on the right side + p1 + |--x + p0 + """ + return self.v.cross(self.z_axis) + + def normal(self, t=0): + """ + Line perpendicular on plane defined by z_axis + lie on the right side + p1 + |--x + p0 + """ + n = GlLine() + n.p = self.lerp(t) + n.v = self.cross + return n + + def sized_normal(self, t, size): + """ + GlLine perpendicular on plane defined by z_axis and of given size + positionned at t in current line + lie on the right side + p1 + |--x + p0 + """ + n = GlLine() + n.p = self.lerp(t) + n.v = size * self.cross.normalized() + return n + + def lerp(self, t): + """ + Interpolate along segment + t parameter [0, 1] where 0 is start of arc and 1 is end + """ + return self.p + self.v * t + + def offset(self, offset): + """ + offset > 0 on the right part + """ + self.p += offset * self.cross.normalized() + + def point_sur_segment(self, pt): + """ point_sur_segment (2d) + point: Vector 3d + t: param t de l'intersection sur le segment courant + d: distance laterale perpendiculaire positif a droite + """ + dp = (pt - self.p).to_2d() + v2d = self.v.to_2d() + dl = v2d.length + d = (self.v.x * dp.y - self.v.y * dp.x) / dl + t = (v2d * dp) / (dl * dl) + return t > 0 and t < 1, d, t + + @property + def pts(self): + return [self.p0, self.p1] + + +class GlCircle(GlBaseLine): + + def __init__(self, + d=3, + radius=0, + center=Vector((0, 0, 0)), + z_axis=Vector((0, 0, 1))): + + self.r = radius + self.c = center + z = z_axis + + if z.z < 1: + x = z.cross(Vector((0, 0, 1))) + y = x.cross(z) + else: + x = Vector((1, 0, 0)) + y = Vector((0, 1, 0)) + + self.rM = Matrix([ + Vector((x.x, y.x, z.x)), + Vector((x.y, y.y, z.y)), + Vector((x.z, y.z, z.z)) + ]) + self.z_axis = z + self.a0 = 0 + self.da = 2 * pi + GlBaseLine.__init__(self, d) + + def lerp(self, t): + """ + Linear interpolation + """ + a = self.a0 + t * self.da + return self.c + self.rM * Vector((self.r * cos(a), self.r * sin(a), 0)) + + @property + def pts(self): + n_pts = max(1, int(round(abs(self.da) / pi * 30, 0))) + t_step = 1 / n_pts + return [self.lerp(i * t_step) for i in range(n_pts + 1)] + + +class GlArc(GlCircle): + + def __init__(self, + d=3, + radius=0, + center=Vector((0, 0, 0)), + z_axis=Vector((0, 0, 1)), + a0=0, + da=0): + """ + a0 and da arguments are in radians + a0 = 0 on the x+ axis side + a0 = pi on the x- axis side + da > 0 CCW contrary-clockwise + da < 0 CW clockwise + """ + GlCircle.__init__(self, d, radius, center, z_axis) + self.da = da + self.a0 = a0 + + @property + def length(self): + return self.r * abs(self.da) + + def normal(self, t=0): + """ + perpendicular line always on the right side + """ + n = GlLine(d=self.d, z_axis=self.z_axis) + n.p = self.lerp(t) + if self.da < 0: + n.v = self.c - n.p + else: + n.v = n.p - self.c + return n + + def sized_normal(self, t, size): + n = GlLine(d=self.d, z_axis=self.z_axis) + n.p = self.lerp(t) + if self.da < 0: + n.v = size * (self.c - n.p).normalized() + else: + n.v = size * (n.p - self.c).normalized() + return n + + def tangeant(self, t, length): + a = self.a0 + t * self.da + ca = cos(a) + sa = sin(a) + n = GlLine(d=self.d, z_axis=self.z_axis) + n.p = self.c + self.rM * Vector((self.r * ca, self.r * sa, 0)) + n.v = self.rM * Vector((length * sa, -length * ca, 0)) + if self.da > 0: + n.v = -n.v + return n + + def offset(self, offset): + """ + offset > 0 on the right part + """ + if self.da > 0: + radius = self.r + offset + else: + radius = self.r - offset + return GlArc(d=self.d, + radius=radius, + center=self.c, + a0=self.a0, + da=self.da, + z_axis=self.z_axis) + + +class GlPolygon(Gl): + + def __init__(self, + colour=(0.0, 0.0, 0.0, 1.0), + d=3): + + self.pts_3d = [] + Gl.__init__(self, d, colour) + + def set_pos(self, pts_3d): + self.pts_3d = pts_3d + + @property + def pts(self): + return self.pts_3d + + def draw(self, context, render=False): + """ + render flag when rendering + """ + self.render = render + bgl.glPushAttrib(bgl.GL_ENABLE_BIT) + bgl.glEnable(bgl.GL_BLEND) + if render: + # enable anti-alias on polygons + bgl.glEnable(bgl.GL_POLYGON_SMOOTH) + bgl.glColor4f(*self.colour) + bgl.glBegin(bgl.GL_POLYGON) + + for pt in self.pts: + x, y = self.position_2d_from_coord(context, pt, render) + bgl.glVertex2f(x, y) + self._end() + + +class GlRect(GlPolygon): + def __init__(self, + colour=(0.0, 0.0, 0.0, 1.0), + d=2): + GlPolygon.__init__(self, colour, d) + + def draw(self, context, render=False): + self.render = render + bgl.glPushAttrib(bgl.GL_ENABLE_BIT) + bgl.glEnable(bgl.GL_BLEND) + if render: + # enable anti-alias on polygons + bgl.glEnable(bgl.GL_POLYGON_SMOOTH) + bgl.glColor4f(*self.colour) + p0 = self.pts[0] + p1 = self.pts[1] + bgl.glRectf(p0.x, p0.y, p1.x, p1.y) + self._end() + + +class GlImage(Gl): + def __init__(self, + d=2, + image=None): + self.image = image + self.colour_inactive = (1, 1, 1, 1) + Gl.__init__(self, d) + self.pts_2d = [Vector((0, 0)), Vector((10, 10))] + + def set_pos(self, pts): + self.pts_2d = pts + + @property + def pts(self): + return self.pts_2d + + def draw(self, context, render=False): + if self.image is None: + return + bgl.glPushAttrib(bgl.GL_ENABLE_BIT) + p0 = self.pts[0] + p1 = self.pts[1] + bgl.glEnable(bgl.GL_BLEND) + bgl.glColor4f(*self.colour) + bgl.glRectf(p0.x, p0.y, p1.x, p1.y) + self.image.gl_load() + bgl.glEnable(bgl.GL_BLEND) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.image.bindcode[0]) + bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_NEAREST) + bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_NEAREST) + bgl.glEnable(bgl.GL_TEXTURE_2D) + bgl.glBlendFunc(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA) + # bgl.glColor4f(1, 1, 1, 1) + bgl.glBegin(bgl.GL_QUADS) + bgl.glTexCoord2d(0, 0) + bgl.glVertex2d(p0.x, p0.y) + bgl.glTexCoord2d(0, 1) + bgl.glVertex2d(p0.x, p1.y) + bgl.glTexCoord2d(1, 1) + bgl.glVertex2d(p1.x, p1.y) + bgl.glTexCoord2d(1, 0) + bgl.glVertex2d(p1.x, p0.y) + bgl.glEnd() + self.image.gl_free() + bgl.glDisable(bgl.GL_TEXTURE_2D) + + +class GlPolyline(GlBaseLine): + def __init__(self, colour, d=3): + self.pts_3d = [] + GlBaseLine.__init__(self, d) + self.colour_inactive = colour + + def set_pos(self, pts_3d): + self.pts_3d = pts_3d + # self.pts_3d.append(pts_3d[0]) + + @property + def pts(self): + return self.pts_3d + + +class GlHandle(GlPolygon): + + def __init__(self, sensor_size, size, draggable=False, selectable=False, d=3): + """ + sensor_size : 2d size in pixels of sensor area + size : 3d size of handle + """ + GlPolygon.__init__(self, d=d) + self.colour_active = (1.0, 0.0, 0.0, 1.0) + self.colour_hover = (1.0, 1.0, 0.0, 1.0) + self.colour_normal = (1.0, 1.0, 1.0, 1.0) + self.colour_selected = (0.0, 0.0, 0.7, 1.0) + self.size = size + self.sensor_width = sensor_size + self.sensor_height = sensor_size + self.pos_3d = Vector((0, 0, 0)) + self.up_axis = Vector((0, 0, 0)) + self.c_axis = Vector((0, 0, 0)) + self.hover = False + self.active = False + self.draggable = draggable + self.selectable = selectable + self.selected = False + + def set_pos(self, context, pos_3d, direction, normal=Vector((0, 0, 1))): + self.up_axis = direction.normalized() + self.c_axis = self.up_axis.cross(normal) + self.pos_3d = pos_3d + self.pos_2d = self.position_2d_from_coord(context, self.sensor_center) + + def check_hover(self, pos_2d): + if self.draggable: + dp = pos_2d - self.pos_2d + self.hover = abs(dp.x) < self.sensor_width and abs(dp.y) < self.sensor_height + + @property + def sensor_center(self): + pts = self.pts + n = len(pts) + x, y, z = 0, 0, 0 + for pt in pts: + x += pt.x + y += pt.y + z += pt.z + return Vector((x / n, y / n, z / n)) + + @property + def pts(self): + raise NotImplementedError + + @property + def colour(self): + if self.render: + return self.colour_inactive + elif self.draggable: + if self.active: + return self.colour_active + elif self.hover: + return self.colour_hover + elif self.selected: + return self.colour_selected + return self.colour_normal + else: + return self.colour_inactive + + +class SquareHandle(GlHandle): + + def __init__(self, sensor_size, size, draggable=False, selectable=False): + GlHandle.__init__(self, sensor_size, size, draggable, selectable) + + @property + def pts(self): + n = self.up_axis + c = self.c_axis + if self.selected or self.hover or self.active: + scale = 1 + else: + scale = 0.5 + x = n * self.size * scale + y = c * self.size * scale + return [self.pos_3d - x - y, self.pos_3d + x - y, self.pos_3d + x + y, self.pos_3d - x + y] + + +class TriHandle(GlHandle): + + def __init__(self, sensor_size, size, draggable=False, selectable=False): + GlHandle.__init__(self, sensor_size, size, draggable, selectable) + + @property + def pts(self): + n = self.up_axis + c = self.c_axis + # does move sensitive area so disable for tri handle + # may implement sensor_center property to fix this + # if self.selected or self.hover or self.active: + scale = 1 + # else: + # scale = 0.5 + x = n * self.size * 4 * scale + y = c * self.size * scale + return [self.pos_3d - x + y, self.pos_3d - x - y, self.pos_3d] + + +class EditableText(GlText, GlHandle): + def __init__(self, sensor_size, size, draggable=False, selectable=False): + GlHandle.__init__(self, sensor_size, size, draggable, selectable) + GlText.__init__(self, colour=(0, 0, 0, 1)) + + def set_pos(self, context, value, pos_3d, direction, normal=Vector((0, 0, 1))): + self.up_axis = direction.normalized() + self.c_axis = self.up_axis.cross(normal) + self.pos_3d = pos_3d + self.value = value + self._text = self.add_units(context) + x, y = self.text_size(context) + self.pos_2d = self.position_2d_from_coord(context, pos_3d) + self.pos_2d.x += 0.5 * x + self.sensor_width, self.sensor_height = 0.5 * x, y + + @property + def sensor_center(self): + return self.pos_3d + + +class ThumbHandle(GlHandle): + + def __init__(self, size_2d, label, image=None, draggable=False, selectable=False, d=2): + GlHandle.__init__(self, size_2d, size_2d, draggable, selectable, d) + self.image = GlImage(image=image) + self.label = GlText(d=2, label=label.replace("_", " ").capitalize()) + self.frame = GlPolyline((1, 1, 1, 1), d=2) + self.frame.closed = True + self.size_2d = size_2d + self.sensor_width = 0.5 * size_2d.x + self.sensor_height = 0.5 * size_2d.y + self.colour_normal = (0.715, 0.905, 1, 0.9) + self.colour_hover = (1, 1, 1, 1) + + def set_pos(self, context, pos_2d): + """ + pos 2d is center !! + """ + self.pos_2d = pos_2d + ts = self.label.text_size(context) + self.label.pos_3d = pos_2d + Vector((-0.5 * ts.x, ts.y - 0.5 * self.size_2d.y)) + p0, p1 = self.pts + self.image.set_pos(self.pts) + self.frame.set_pos([p0, Vector((p1.x, p0.y)), p1, Vector((p0.x, p1.y))]) + + @property + def pts(self): + s = 0.5 * self.size_2d + return [self.pos_2d - s, self.pos_2d + s] + + @property + def sensor_center(self): + return self.pos_2d + 0.5 * self.size_2d + + def draw(self, context, render=False): + self.render = render + self.image.colour_inactive = self.colour + GlHandle.draw(self, context, render=False) + self.image.draw(context, render=False) + self.label.draw(context, render=False) + self.frame.draw(context, render=False) + + +class Screen(): + def __init__(self, margin): + self.margin = margin + + def size(self, context): + + system = context.user_preferences.system + w = context.region.width + h = context.region.height + y_min = self.margin + y_max = h - self.margin + x_min = self.margin + x_max = w - self.margin + if (system.use_region_overlap and + system.window_draw_method in {'TRIPLE_BUFFER', 'AUTOMATIC'}): + area = context.area + + for r in area.regions: + if r.type == 'TOOLS': + x_min += r.width + elif r.type == 'UI': + x_max -= r.width + return x_min, x_max, y_min, y_max + + +class FeedbackPanel(): + """ + Feed-back panel + inspired by np_station + """ + def __init__(self, title='Archipack'): + + prefs = self.get_prefs(bpy.context) + + self.main_title = GlText(d=2, + label=title + " : ", + font_size=prefs.feedback_size_main, + colour=prefs.feedback_colour_main + ) + self.title = GlText(d=2, + font_size=prefs.feedback_size_title, + colour=prefs.feedback_colour_main + ) + self.spacing = Vector(( + 0.5 * prefs.feedback_size_shortcut, + 0.5 * prefs.feedback_size_shortcut)) + self.margin = 50 + self.explanation = GlText(d=2, + font_size=prefs.feedback_size_shortcut, + colour=prefs.feedback_colour_main + ) + self.shortcut_area = GlPolygon(colour=prefs.feedback_shortcut_area, d=2) + self.title_area = GlPolygon(colour=prefs.feedback_title_area, d=2) + self.shortcuts = [] + self.on = False + self.show_title = True + self.show_main_title = True + # read only, when enabled, after draw() the top left coord of info box + self.top = Vector((0, 0)) + self.screen = Screen(self.margin) + + def disable(self): + self.on = False + + def enable(self): + self.on = True + + def get_prefs(self, context): + global __name__ + try: + # retrieve addon name from imports + addon_name = __name__.split('.')[0] + prefs = context.user_preferences.addons[addon_name].preferences + except: + prefs = DefaultColorScheme + pass + return prefs + + def instructions(self, context, title, explanation, shortcuts): + """ + position from bottom to top + """ + prefs = self.get_prefs(context) + + self.explanation.label = explanation + self.title.label = title + + self.shortcuts = [] + + for key, label in shortcuts: + key = GlText(d=2, label=key, + font_size=prefs.feedback_size_shortcut, + colour=prefs.feedback_colour_key) + label = GlText(d=2, label=' : ' + label, + font_size=prefs.feedback_size_shortcut, + colour=prefs.feedback_colour_shortcut) + ks = key.text_size(context) + ls = label.text_size(context) + self.shortcuts.append([key, ks, label, ls]) + + def draw(self, context, render=False): + if self.on: + """ + draw from bottom to top + so we are able to always fit needs + """ + x_min, x_max, y_min, y_max = self.screen.size(context) + available_w = x_max - x_min - 2 * self.spacing.x + main_title_size = self.main_title.text_size(context) + Vector((5, 0)) + + # h = context.region.height + # 0,0 = bottom left + pos = Vector((x_min + self.spacing.x, y_min)) + shortcuts = [] + + # sort by lines + lines = [] + line = [] + space = 0 + sum_txt = 0 + + for key, ks, label, ls in self.shortcuts: + space += ks.x + ls.x + self.spacing.x + if pos.x + space > available_w: + txt_spacing = (available_w - sum_txt) / (max(1, len(line) - 1)) + sum_txt = 0 + space = ks.x + ls.x + self.spacing.x + lines.append((txt_spacing, line)) + line = [] + sum_txt += ks.x + ls.x + line.append([key, ks, label, ls]) + + if len(line) > 0: + txt_spacing = (available_w - sum_txt) / (max(1, len(line) - 1)) + lines.append((txt_spacing, line)) + + # reverse lines to draw from bottom to top + lines = list(reversed(lines)) + for spacing, line in lines: + pos.y += self.spacing.y + pos.x = x_min + self.spacing.x + for key, ks, label, ls in line: + key.pos_3d = pos.copy() + pos.x += ks.x + label.pos_3d = pos.copy() + pos.x += ls.x + spacing + shortcuts.extend([key, label]) + pos.y += ks.y + self.spacing.y + + n_shortcuts = len(shortcuts) + # shortcut area + self.shortcut_area.pts_3d = [ + (x_min, self.margin), + (x_max, self.margin), + (x_max, pos.y), + (x_min, pos.y) + ] + + # small space between shortcut area and main title bar + if n_shortcuts > 0: + pos.y += 0.5 * self.spacing.y + + self.title_area.pts_3d = [ + (x_min, pos.y), + (x_max, pos.y), + (x_max, pos.y + main_title_size.y + 2 * self.spacing.y), + (x_min, pos.y + main_title_size.y + 2 * self.spacing.y) + ] + pos.y += self.spacing.y + + title_size = self.title.text_size(context) + # check for space available: + # if explanation + title + main_title are too big + # 1 remove main title + # 2 remove title + explanation_size = self.explanation.text_size(context) + + self.show_title = True + self.show_main_title = True + + if title_size.x + explanation_size.x > available_w: + # keep only explanation + self.show_title = False + self.show_main_title = False + elif main_title_size.x + title_size.x + explanation_size.x > available_w: + # keep title + explanation + self.show_main_title = False + self.title.pos_3d = (x_min + self.spacing.x, pos.y) + else: + self.title.pos_3d = (x_min + self.spacing.x + main_title_size.x, pos.y) + + self.explanation.pos_3d = (x_max - self.spacing.x - explanation_size.x, pos.y) + self.main_title.pos_3d = (x_min + self.spacing.x, pos.y) + + self.shortcut_area.draw(context) + self.title_area.draw(context) + if self.show_title: + self.title.draw(context) + if self.show_main_title: + self.main_title.draw(context) + self.explanation.draw(context) + for s in shortcuts: + s.draw(context) + + self.top = Vector((x_min, pos.y + main_title_size.y + self.spacing.y)) + + +class GlCursorFence(): + """ + Cursor crossing Fence + """ + def __init__(self, width=1, colour=(1.0, 1.0, 1.0, 0.5), style=2852): + self.line_x = GlLine(d=2) + self.line_x.style = style + self.line_x.width = width + self.line_x.colour_inactive = colour + self.line_y = GlLine(d=2) + self.line_y.style = style + self.line_y.width = width + self.line_y.colour_inactive = colour + self.on = True + + def set_location(self, context, location): + w = context.region.width + h = context.region.height + x, y = location + self.line_x.p = Vector((0, y)) + self.line_x.v = Vector((w, 0)) + self.line_y.p = Vector((x, 0)) + self.line_y.v = Vector((0, h)) + + def enable(self): + self.on = True + + def disable(self): + self.on = False + + def draw(self, context, render=False): + if self.on: + self.line_x.draw(context) + self.line_y.draw(context) + + +class GlCursorArea(): + def __init__(self, + width=1, + bordercolour=(1.0, 1.0, 1.0, 0.5), + areacolour=(0.5, 0.5, 0.5, 0.08), + style=2852): + + self.border = GlPolyline(bordercolour, d=2) + self.border.style = style + self.border.width = width + self.border.closed = True + self.area = GlPolygon(areacolour, d=2) + self.min = Vector((0, 0)) + self.max = Vector((0, 0)) + self.on = False + + def in_area(self, pt): + return (self.min.x <= pt.x and self.max.x >= pt.x and + self.min.y <= pt.y and self.max.y >= pt.y) + + def set_location(self, context, p0, p1): + x0, y0 = p0 + x1, y1 = p1 + if x0 > x1: + x1, x0 = x0, x1 + if y0 > y1: + y1, y0 = y0, y1 + self.min = Vector((x0, y0)) + self.max = Vector((x1, y1)) + pos = [ + Vector((x0, y0)), + Vector((x0, y1)), + Vector((x1, y1)), + Vector((x1, y0))] + self.area.set_pos(pos) + self.border.set_pos(pos) + + def enable(self): + self.on = True + + def disable(self): + self.on = False + + def draw(self, context, render=False): + if self.on: + self.area.draw(context) + self.border.draw(context) diff --git a/archipack/archipack_handle.py b/archipack/archipack_handle.py new file mode 100644 index 000000000..852fe2b6e --- /dev/null +++ b/archipack/archipack_handle.py @@ -0,0 +1,178 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- + +import bpy + + +def create_handle(context, parent, mesh): + handle = bpy.data.objects.new("Handle", mesh) + handle['archipack_handle'] = True + context.scene.objects.link(handle) + modif = handle.modifiers.new('Subsurf', 'SUBSURF') + modif.render_levels = 4 + modif.levels = 1 + handle.parent = parent + handle.matrix_world = parent.matrix_world.copy() + return handle + + +def door_handle_horizontal_01(direction, side, offset=0): + """ + side 1 -> inside + """ + verts = [(0.015, -0.003, -0.107), (0.008, -0.002, -0.007), (0.015, -0.002, -0.107), + (0.019, -0.002, -0.026), (-0.015, -0.003, -0.107), (-0.007, -0.002, -0.007), + (-0.015, -0.002, -0.107), (-0.018, -0.002, -0.026), (0.008, -0.002, 0.007), + (0.019, -0.002, 0.034), (-0.018, -0.002, 0.034), (-0.007, -0.002, 0.007), + (-0.018, -0.003, -0.026), (0.019, -0.003, -0.026), (-0.018, -0.003, 0.034), + (0.019, -0.003, 0.034), (-0.007, -0.042, -0.007), (0.008, -0.042, -0.007), + (-0.007, -0.042, 0.007), (0.008, -0.042, 0.007), (-0.007, -0.047, -0.016), + (0.008, -0.048, -0.018), (-0.007, -0.047, 0.016), (0.008, -0.048, 0.018), + (-0.025, -0.041, 0.013), (-0.025, -0.041, -0.012), (-0.025, -0.048, 0.013), + (-0.025, -0.048, -0.012), (0.019, -0.0, -0.026), (0.015, -0.0, -0.107), + (-0.015, -0.0, -0.107), (-0.018, -0.0, -0.026), (0.019, 0.0, 0.034), + (-0.018, 0.0, 0.034), (-0.107, -0.041, 0.013), (-0.107, -0.041, -0.012), + (-0.107, -0.048, 0.013), (-0.107, -0.048, -0.012), (-0.12, -0.041, 0.013), + (-0.12, -0.041, -0.012), (-0.12, -0.048, 0.013), (-0.12, -0.048, -0.012), + (0.008, -0.005, -0.007), (0.008, -0.005, 0.007), (-0.007, -0.005, 0.007), + (-0.007, -0.005, -0.007), (0.008, -0.041, -0.007), (0.008, -0.041, 0.007), + (-0.007, -0.041, 0.007), (-0.007, -0.041, -0.007), (0.015, -0.003, -0.091), + (0.015, -0.002, -0.091), (-0.015, -0.002, -0.091), (-0.015, -0.003, -0.091), + (0.015, -0.0, -0.091), (-0.015, -0.0, -0.091), (0.015, -0.003, 0.044), + (0.015, -0.002, 0.044), (-0.015, -0.002, 0.044), (-0.015, -0.003, 0.044), + (0.015, 0.0, 0.044), (-0.015, 0.0, 0.044)] + + faces = [(50, 51, 3, 13), (52, 55, 30, 6), (52, 53, 12, 7), (53, 50, 13, 12), + (2, 0, 4, 6), (10, 33, 31, 7), (15, 56, 59, 14), (12, 14, 10, 7), + (3, 9, 15, 13), (47, 19, 17, 46), (5, 12, 13, 1), (8, 15, 14, 11), + (11, 14, 12, 5), (1, 13, 15, 8), (22, 26, 27, 20), (48, 18, 19, 47), + (49, 16, 18, 48), (46, 17, 16, 49), (21, 23, 22, 20), (17, 21, 20, 16), + (19, 23, 21, 17), (18, 22, 23, 19), (24, 34, 36, 26), (16, 25, 24, 18), + (20, 27, 25, 16), (18, 24, 26, 22), (4, 0, 50, 53), (2, 29, 54, 51), + (6, 30, 29, 2), (10, 58, 61, 33), (3, 28, 32, 9), (51, 54, 28, 3), + (34, 38, 40, 36), (25, 35, 34, 24), (27, 37, 35, 25), (26, 36, 37, 27), + (39, 41, 40, 38), (35, 39, 38, 34), (37, 41, 39, 35), (36, 40, 41, 37), + (1, 42, 45, 5), (5, 45, 44, 11), (11, 44, 43, 8), (8, 43, 42, 1), + (42, 46, 49, 45), (45, 49, 48, 44), (44, 48, 47, 43), (43, 47, 46, 42), + (6, 4, 53, 52), (7, 31, 55, 52), (0, 2, 51, 50), (58, 59, 56, 57), + (57, 60, 61, 58), (32, 60, 57, 9), (14, 59, 58, 10), (9, 57, 56, 15)] + + if side == 1: + if direction == 1: + verts = [(-v[0], -v[1], v[2]) for v in verts] + else: + verts = [(v[0], -v[1], v[2]) for v in verts] + faces = [tuple(reversed(f)) for f in faces] + else: + if direction == 1: + verts = [(-v[0], v[1], v[2]) for v in verts] + faces = [tuple(reversed(f)) for f in faces] + if offset > 0: + faces = [tuple([i + offset for i in f]) for f in faces] + return verts, faces + + +def window_handle_vertical_01(side): + """ + side 1 -> inside + short handle for flat window + """ + verts = [(-0.01, 0.003, 0.011), (-0.013, 0.0, -0.042), (-0.018, 0.003, 0.03), (-0.01, 0.003, -0.01), + (-0.018, 0.003, -0.038), (0.01, 0.003, 0.011), (0.018, 0.003, 0.03), (0.018, 0.003, -0.038), + (0.01, 0.003, -0.01), (-0.018, 0.004, -0.038), (-0.018, 0.004, 0.03), (0.018, 0.004, -0.038), + (0.018, 0.004, 0.03), (-0.01, 0.039, -0.01), (-0.01, 0.025, 0.011), (0.01, 0.036, -0.01), + (0.01, 0.025, 0.011), (-0.017, 0.049, -0.01), (-0.01, 0.034, 0.011), (0.017, 0.049, -0.01), + (0.01, 0.034, 0.011), (0.0, 0.041, -0.048), (0.013, 0.003, 0.033), (0.019, 0.057, -0.048), + (-0.019, 0.057, -0.048), (-0.018, 0.0, 0.03), (0.013, 0.0, -0.042), (0.013, 0.004, -0.042), + (-0.018, 0.0, -0.038), (0.018, 0.0, 0.03), (0.018, 0.0, -0.038), (0.001, 0.041, -0.126), + (-0.013, 0.004, 0.033), (0.019, 0.056, -0.126), (-0.019, 0.056, -0.126), (0.001, 0.036, -0.16), + (-0.013, 0.003, 0.033), (0.019, 0.051, -0.16), (-0.019, 0.051, -0.16), (-0.01, 0.006, 0.011), + (0.01, 0.006, 0.011), (0.01, 0.006, -0.01), (-0.01, 0.006, -0.01), (-0.01, 0.025, 0.011), + (0.01, 0.025, 0.011), (0.01, 0.035, -0.01), (-0.01, 0.038, -0.01), (0.013, 0.003, -0.042), + (-0.013, 0.0, 0.033), (-0.013, 0.004, -0.042), (-0.013, 0.003, -0.042), (0.013, 0.004, 0.033), + (0.013, 0.0, 0.033)] + + faces = [(4, 2, 10, 9), (6, 12, 51, 22), (10, 2, 36, 32), (2, 25, 48, 36), + (27, 47, 50, 49), (7, 30, 26, 47), (28, 4, 50, 1), (12, 10, 32, 51), + (16, 14, 43, 44), (9, 10, 0, 3), (12, 11, 8, 5), (11, 9, 3, 8), + (10, 12, 5, 0), (23, 24, 17, 19), (15, 16, 44, 45), (13, 15, 45, 46), + (14, 13, 46, 43), (20, 19, 17, 18), (18, 17, 13, 14), (20, 18, 14, 16), + (19, 20, 16, 15), (31, 33, 23, 21), (21, 15, 13), (24, 21, 13, 17), + (21, 23, 19, 15), (9, 11, 27, 49), (26, 1, 50, 47), (4, 9, 49, 50), + (29, 6, 22, 52), (35, 37, 33, 31), (48, 52, 22, 36), (34, 31, 21, 24), + (33, 34, 24, 23), (38, 37, 35), (22, 51, 32, 36), (38, 35, 31, 34), + (37, 38, 34, 33), (39, 42, 3, 0), (42, 41, 8, 3), (41, 40, 5, 8), + (40, 39, 0, 5), (43, 46, 42, 39), (46, 45, 41, 42), (45, 44, 40, 41), + (44, 43, 39, 40), (28, 25, 2, 4), (12, 6, 7, 11), (7, 6, 29, 30), + (11, 7, 47, 27)] + + if side == 0: + verts = [(v[0], -v[1], v[2]) for v in verts] + faces = [tuple(reversed(f)) for f in faces] + + return verts, faces + + +def window_handle_vertical_02(side): + """ + side 1 -> inside + long handle for rail windows + """ + verts = [(-0.01, 0.003, 0.011), (-0.013, 0.0, -0.042), (-0.018, 0.003, 0.03), (-0.01, 0.003, -0.01), + (-0.018, 0.003, -0.038), (0.01, 0.003, 0.011), (0.018, 0.003, 0.03), (0.018, 0.003, -0.038), + (0.01, 0.003, -0.01), (-0.018, 0.004, -0.038), (-0.018, 0.004, 0.03), (0.018, 0.004, -0.038), + (0.018, 0.004, 0.03), (-0.01, 0.041, -0.01), (-0.01, 0.027, 0.011), (0.01, 0.038, -0.01), + (0.01, 0.027, 0.011), (-0.017, 0.054, -0.01), (-0.01, 0.039, 0.011), (0.017, 0.054, -0.01), + (0.01, 0.039, 0.011), (0.0, 0.041, -0.048), (0.013, 0.003, 0.033), (0.019, 0.059, -0.048), + (-0.019, 0.059, -0.048), (-0.018, 0.0, 0.03), (0.013, 0.0, -0.042), (0.013, 0.004, -0.042), + (-0.018, 0.0, -0.038), (0.018, 0.0, 0.03), (0.018, 0.0, -0.038), (0.001, 0.041, -0.322), + (-0.013, 0.004, 0.033), (0.019, 0.058, -0.322), (-0.019, 0.058, -0.322), (0.001, 0.036, -0.356), + (-0.013, 0.003, 0.033), (0.019, 0.053, -0.356), (-0.019, 0.053, -0.356), (-0.01, 0.006, 0.011), + (0.01, 0.006, 0.011), (0.01, 0.006, -0.01), (-0.01, 0.006, -0.01), (-0.01, 0.027, 0.011), + (0.01, 0.027, 0.011), (0.01, 0.037, -0.01), (-0.01, 0.04, -0.01), (0.013, 0.003, -0.042), + (-0.013, 0.0, 0.033), (-0.013, 0.004, -0.042), (-0.013, 0.003, -0.042), (0.013, 0.004, 0.033), + (0.013, 0.0, 0.033)] + + faces = [(4, 2, 10, 9), (6, 12, 51, 22), (10, 2, 36, 32), (2, 25, 48, 36), + (27, 47, 50, 49), (7, 30, 26, 47), (28, 4, 50, 1), (12, 10, 32, 51), + (16, 14, 43, 44), (9, 10, 0, 3), (12, 11, 8, 5), (11, 9, 3, 8), + (10, 12, 5, 0), (23, 24, 17, 19), (15, 16, 44, 45), (13, 15, 45, 46), + (14, 13, 46, 43), (20, 19, 17, 18), (18, 17, 13, 14), (20, 18, 14, 16), + (19, 20, 16, 15), (31, 33, 23, 21), (21, 15, 13), (24, 21, 13, 17), + (21, 23, 19, 15), (9, 11, 27, 49), (26, 1, 50, 47), (4, 9, 49, 50), + (29, 6, 22, 52), (35, 37, 33, 31), (48, 52, 22, 36), (34, 31, 21, 24), + (33, 34, 24, 23), (38, 37, 35), (22, 51, 32, 36), (38, 35, 31, 34), + (37, 38, 34, 33), (39, 42, 3, 0), (42, 41, 8, 3), (41, 40, 5, 8), + (40, 39, 0, 5), (43, 46, 42, 39), (46, 45, 41, 42), (45, 44, 40, 41), + (44, 43, 39, 40), (28, 25, 2, 4), (12, 6, 7, 11), (7, 6, 29, 30), + (11, 7, 47, 27)] + + if side == 0: + verts = [(v[0], -v[1], v[2]) for v in verts] + faces = [tuple(reversed(f)) for f in faces] + + return verts, faces diff --git a/archipack/archipack_keymaps.py b/archipack/archipack_keymaps.py new file mode 100644 index 000000000..65b295bfd --- /dev/null +++ b/archipack/archipack_keymaps.py @@ -0,0 +1,108 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- + + +class Keymaps: + """ + Expose user defined keymaps as event + so in modal operator we are able to + identify like + if (event == keymap.undo.event): + + and in feedback panels: + keymap.undo.key + keymap.undo.name + """ + def __init__(self, context): + """ + Init keymaps properties + """ + + # undo event + self.undo = self.get_event(context, 'Screen', 'ed.undo') + + # delete event + self.delete = self.get_event(context, 'Object Mode', 'object.delete') + + """ + # provide abstration between user and addon + # with different select mouse side + mouse_right = context.user_preferences.inputs.select_mouse + if mouse_right == 'LEFT': + mouse_left = 'RIGHT' + mouse_right_side = 'Left' + mouse_left_side = 'Right' + else: + mouse_left = 'LEFT' + mouse_right_side = 'Right' + mouse_left_side = 'Left' + + self.leftmouse = mouse_left + 'MOUSE' + self.rightmouse = mouse_right + 'MOUSE' + """ + + def check(self, event, against): + return against['event'] == (event.alt, event.ctrl, event.shift, event.type, event.value) + + def get_event(self, context, keyconfig, keymap_item): + """ + Return simple keymaps event signature as dict + NOTE: + this won't work for complex keymaps such as select_all + using properties to call operator in different manner + type: keyboard main type + name: event name as defined in user preferences + event: simple event signature to compare like : + if event == keymap.undo.event: + """ + ev = context.window_manager.keyconfigs.user.keymaps[keyconfig].keymap_items[keymap_item] + key = ev.type + if ev.ctrl: + key += '+CTRL' + if ev.alt: + key += '+ALT' + if ev.shift: + key += '+SHIFT' + return {'type': key, 'name': ev.name, 'event': (ev.alt, ev.ctrl, ev.shift, ev.type, ev.value)} + + def dump_keys(self, context, filename="c:\\tmp\\keymap.txt"): + """ + Utility for developpers : + Dump all keymaps to a file + filename : string a file path to dump keymaps + """ + str = "" + km = context.window_manager.keyconfigs.user.keymaps + for key in km.keys(): + str += "\n\n#--------------------------------\n{}:\n#--------------------------------\n\n".format(key) + for sub in km[key].keymap_items.keys(): + k = km[key].keymap_items[sub] + str += "alt:{} ctrl:{} shift:{} type:{} value:{} idname:{} name:{}\n".format( + k.alt, k.ctrl, k.shift, k.type, k.value, sub, k.name) + file = open(filename, "w") + file.write(str) + file.close() diff --git a/archipack/archipack_manipulator.py b/archipack/archipack_manipulator.py new file mode 100644 index 000000000..c3e0fc24b --- /dev/null +++ b/archipack/archipack_manipulator.py @@ -0,0 +1,2446 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +import bpy +from math import atan2, pi +from mathutils import Vector, Matrix +from mathutils.geometry import intersect_line_plane, intersect_point_line, intersect_line_sphere +from bpy_extras import view3d_utils +from bpy.types import PropertyGroup, Operator +from bpy.props import FloatVectorProperty, StringProperty, CollectionProperty, BoolProperty +from bpy.app.handlers import persistent +from .archipack_snap import snap_point +from .archipack_keymaps import Keymaps +from .archipack_gl import ( + GlLine, GlArc, GlText, + GlPolyline, GlPolygon, + TriHandle, SquareHandle, EditableText, + FeedbackPanel, GlCursorArea +) + + +# NOTE: +# Snap aware manipulators use a dirty hack : +# draw() as a callback to update values in realtime +# as transform.translate in use to allow snap +# does catch all events. +# This however has a wanted side effect: +# the manipulator take precedence over allready running +# ones, and prevent select mode to start. +# +# TODO: +# Other manipulators should use same technique to take +# precedence over allready running ones when active +# +# NOTE: +# Select mode does suffer from this stack effect: +# the last running wins. The point is left mouse select mode +# requiring left drag to be RUNNING_MODAL to prevent real +# objects select and move during manipulators selection. +# +# TODO: +# First run a separate modal dedicated to select mode. +# Selecting in whole manips stack when required +# (manips[key].manipulable.manip_stack) +# Must investigate for a way to handle unselect after drag done. + +""" + @TODO: + Last modal running wins. + Manipulateurs without snap and thus not running own modal, + may loose events events caught by select mode of last + manipulable enabled +""" + +# Arrow sizes (world units) +arrow_size = 0.05 +# Handle area size (pixels) +handle_size = 10 + + +# a global manipulator stack reference +# prevent Blender "ACCESS_VIOLATION" crashes +# use a dict to prevent collisions +# between many objects being in manipulate mode +# use object names as loose keys +# NOTE : use app.drivers to reset before file load +manips = {} + + +class ArchipackActiveManip: + """ + Store manipulated object + - object_name: manipulated object name + - stack: array of Manipulators instances + - manipulable: Manipulable instance + """ + def __init__(self, object_name): + self.object_name = object_name + # manipulators stack for object + self.stack = [] + # reference to object manipulable instance + self.manipulable = None + + @property + def dirty(self): + """ + Check for manipulable validity + to disable modal when required + """ + return ( + self.manipulable is None or + bpy.data.objects.find(self.object_name) < 0 + ) + + def exit(self): + """ + Exit manipulation mode + - exit from all running manipulators + - empty manipulators stack + - set manipulable.manipulate_mode to False + - remove reference to manipulable + """ + for m in self.stack: + if m is not None: + m.exit() + if self.manipulable is not None: + self.manipulable.manipulate_mode = False + self.manipulable = None + self.object_name = "" + self.stack.clear() + + +def remove_manipulable(key): + """ + disable and remove a manipulable from stack + """ + global manips + # print("remove_manipulable key:%s" % (key)) + if key in manips.keys(): + manips[key].exit() + manips.pop(key) + + +def check_stack(key): + """ + check for stack item validity + use in modal to destroy invalid modals + return true when invalid / not found + false when valid + """ + global manips + if key not in manips.keys(): + # print("check_stack : key not found %s" % (key)) + return True + elif manips[key].dirty: + # print("check_stack : key.dirty %s" % (key)) + remove_manipulable(key) + return True + + return False + + +def empty_stack(): + # print("empty_stack()") + """ + kill every manipulators in stack + and cleanup stack + """ + global manips + for key in manips.keys(): + manips[key].exit() + manips.clear() + + +def add_manipulable(key, manipulable): + """ + add a ArchipackActiveManip into the stack + if not allready present + setup reference to manipulable + return manipulators stack + """ + global manips + if key not in manips.keys(): + # print("add_manipulable() key:%s not found create new" % (key)) + manips[key] = ArchipackActiveManip(key) + + manips[key].manipulable = manipulable + return manips[key].stack + + +# ------------------------------------------------------------------ +# Define Manipulators +# ------------------------------------------------------------------ + + +class Manipulator(): + """ + Manipulator base class to derive other + handle keyboard and modal events + provide convenient funcs including getter and setter for datablock values + store reference of base object, datablock and manipulator + """ + keyboard_ascii = { + ".", ",", "-", "+", "1", "2", "3", + "4", "5", "6", "7", "8", "9", "0", + "c", "m", "d", "k", "h", "a", + " ", "/", "*", "'", "\"" + # "=" + } + keyboard_type = { + 'BACK_SPACE', 'DEL', + 'LEFT_ARROW', 'RIGHT_ARROW' + } + + def __init__(self, context, o, datablock, manipulator, snap_callback=None): + """ + o : object to manipulate + datablock : object data to manipulate + manipulator: object archipack_manipulator datablock + snap_callback: on snap enabled manipulators, will be called when drag occurs + """ + self.keymap = Keymaps(context) + self.feedback = FeedbackPanel() + self.active = False + self.selectable = False + self.selected = False + # active text input value for manipulator + self.keyboard_input_active = False + self.label_value = 0 + # unit for keyboard input value + self.value_type = 'LENGTH' + self.pts_mode = 'SIZE' + self.o = o + self.datablock = datablock + self.manipulator = manipulator + self.snap_callback = snap_callback + self.origin = Vector((0, 0, 1)) + self.mouse_pos = Vector((0, 0)) + self.length_entered = "" + self.line_pos = 0 + args = (self, context) + self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback, args, 'WINDOW', 'POST_PIXEL') + + @classmethod + def poll(cls, context): + """ + Allow manipulator enable/disable + in given context + handles will not show + """ + return True + + def exit(self): + """ + Modal exit, DONT EVEN TRY TO OVERRIDE + """ + if self._handle is not None: + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + self._handle = None + else: + print("Manipulator.exit() handle not found %s" % (type(self).__name__)) + + # Mouse event handlers, MUST be overriden + def mouse_press(self, context, event): + """ + Manipulators must implement + mouse press event handler + return True to callback manipulable_manipulate + """ + raise NotImplementedError + + def mouse_release(self, context, event): + """ + Manipulators must implement + mouse mouse_release event handler + return False to callback manipulable_release + """ + raise NotImplementedError + + def mouse_move(self, context, event): + """ + Manipulators must implement + mouse move event handler + return True to callback manipulable_manipulate + """ + raise NotImplementedError + + # Keyboard event handlers, MAY be overriden + def keyboard_done(self, context, event, value): + """ + Manipulators may implement + keyboard value validated event handler + value: changed by keyboard + return True to callback manipulable_manipulate + """ + return False + + def keyboard_editing(self, context, event, value): + """ + Manipulators may implement + keyboard value changed event handler + value: string changed by keyboard + allow realtime update of label + return False to show edited value on window header + return True when feedback show right on screen + """ + self.label_value = value + return True + + def keyboard_cancel(self, context, event): + """ + Manipulators may implement + keyboard entry cancelled + """ + return + + def cancel(self, context, event): + """ + Manipulators may implement + cancelled event (ESC RIGHTCLICK) + """ + self.active = False + return + + def undo(self, context, event): + """ + Manipulators may implement + undo event (CTRL+Z) + """ + return False + + # Internal, do not override unless you realy + # realy realy deeply know what you are doing + def keyboard_eval(self, context, event): + """ + evaluate keyboard entry while typing + do not override this one + """ + c = event.ascii + if c: + if c == ",": + c = "." + self.length_entered = self.length_entered[:self.line_pos] + c + self.length_entered[self.line_pos:] + self.line_pos += 1 + + if self.length_entered: + if event.type == 'BACK_SPACE': + self.length_entered = self.length_entered[:self.line_pos - 1] + self.length_entered[self.line_pos:] + self.line_pos -= 1 + + elif event.type == 'DEL': + self.length_entered = self.length_entered[:self.line_pos] + self.length_entered[self.line_pos + 1:] + + elif event.type == 'LEFT_ARROW': + self.line_pos = (self.line_pos - 1) % (len(self.length_entered) + 1) + + elif event.type == 'RIGHT_ARROW': + self.line_pos = (self.line_pos + 1) % (len(self.length_entered) + 1) + + try: + value = bpy.utils.units.to_value(context.scene.unit_settings.system, self.value_type, self.length_entered) + draw_on_header = self.keyboard_editing(context, event, value) + except: # ValueError: + draw_on_header = True + pass + + if draw_on_header: + a = "" + if self.length_entered: + pos = self.line_pos + a = self.length_entered[:pos] + '|' + self.length_entered[pos:] + context.area.header_text_set("%s" % (a)) + + # modal mode: do not let event bubble up + return True + + def modal(self, context, event): + """ + Modal handler + handle mouse, and keyboard events + enable and disable feedback + """ + # print("Manipulator modal:%s %s" % (event.value, event.type)) + + if event.type == 'MOUSEMOVE': + return self.mouse_move(context, event) + + elif event.value == 'PRESS': + + if event.type == 'LEFTMOUSE': + active = self.mouse_press(context, event) + if active: + self.feedback.enable() + return active + + elif self.keymap.check(event, self.keymap.undo): + if self.keyboard_input_active: + self.keyboard_input_active = False + self.keyboard_cancel(context, event) + self.feedback.disable() + # prevent undo CRASH + return True + + elif self.keyboard_input_active and ( + event.ascii in self.keyboard_ascii or + event.type in self.keyboard_type + ): + # get keyboard input + return self.keyboard_eval(context, event) + + elif event.type in {'ESC', 'RIGHTMOUSE'}: + self.feedback.disable() + if self.keyboard_input_active: + # allow keyboard exit without setting value + self.length_entered = "" + self.line_pos = 0 + self.keyboard_input_active = False + self.keyboard_cancel(context, event) + return True + elif self.active: + self.cancel(context, event) + return True + return False + + elif event.value == 'RELEASE': + + if event.type == 'LEFTMOUSE': + if not self.keyboard_input_active: + self.feedback.disable() + return self.mouse_release(context, event) + + elif self.keyboard_input_active and event.type in {'RET', 'NUMPAD_ENTER'}: + # validate keyboard input + if self.length_entered != "": + try: + value = bpy.utils.units.to_value( + context.scene.unit_settings.system, + self.value_type, self.length_entered) + self.length_entered = "" + ret = self.keyboard_done(context, event, value) + except: # ValueError: + ret = False + self.keyboard_cancel(context, event) + pass + context.area.header_text_set() + self.keyboard_input_active = False + self.feedback.disable() + return ret + + return False + + def mouse_position(self, event): + """ + store mouse position in a 2d Vector + """ + self.mouse_pos.x, self.mouse_pos.y = event.mouse_region_x, event.mouse_region_y + + def get_pos3d(self, context): + """ + convert mouse pos to 3d point over plane defined by origin and normal + pt is in world space + """ + region = context.region + rv3d = context.region_data + rM = context.active_object.matrix_world.to_3x3() + view_vector_mouse = view3d_utils.region_2d_to_vector_3d(region, rv3d, self.mouse_pos) + ray_origin_mouse = view3d_utils.region_2d_to_origin_3d(region, rv3d, self.mouse_pos) + pt = intersect_line_plane(ray_origin_mouse, ray_origin_mouse + view_vector_mouse, + self.origin, rM * self.manipulator.normal, False) + # fix issue with parallel plane + if pt is None: + pt = intersect_line_plane(ray_origin_mouse, ray_origin_mouse + view_vector_mouse, + self.origin, view_vector_mouse, False) + return pt + + def get_value(self, data, attr, index=-1): + """ + Datablock value getter with index support + """ + try: + if index > -1: + return getattr(data, attr)[index] + else: + return getattr(data, attr) + except: + print("get_value of %s %s failed" % (data, attr)) + return 0 + + def set_value(self, context, data, attr, value, index=-1): + """ + Datablock value setter with index support + """ + try: + if self.get_value(data, attr, index) != value: + # switch context so unselected object may be manipulable too + old = context.active_object + state = self.o.select + self.o.select = True + context.scene.objects.active = self.o + if index > -1: + getattr(data, attr)[index] = value + else: + setattr(data, attr, value) + self.o.select = state + old.select = True + context.scene.objects.active = old + except: + pass + + def preTranslate(self, tM, vec): + """ + return a preTranslated Matrix + tM Matrix source + vec Vector translation + """ + return tM * Matrix([ + [1, 0, 0, vec.x], + [0, 1, 0, vec.y], + [0, 0, 1, vec.z], + [0, 0, 0, 1]]) + + def _move(self, o, axis, value): + if axis == 'x': + vec = Vector((value, 0, 0)) + elif axis == 'y': + vec = Vector((0, value, 0)) + else: + vec = Vector((0, 0, value)) + o.matrix_world = self.preTranslate(o.matrix_world, vec) + + def move_linked(self, context, axis, value): + """ + Move an object along local axis + takes care of linked too, fix issue #8 + """ + old = context.active_object + bpy.ops.object.select_all(action='DESELECT') + self.o.select = True + context.scene.objects.active = self.o + bpy.ops.object.select_linked(type='OBDATA') + for o in context.selected_objects: + if o != self.o: + self._move(o, axis, value) + bpy.ops.object.select_all(action='DESELECT') + old.select = True + context.scene.objects.active = old + + def move(self, context, axis, value): + """ + Move an object along local axis + """ + self._move(self.o, axis, value) + + +# OUT OF ORDER +class SnapPointManipulator(Manipulator): + """ + np_station based snap manipulator + dosent update anything by itself. + NOTE : currently out of order + and disabled in __init__ + """ + def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None): + + raise NotImplementedError + + self.handle = SquareHandle(handle_size, 1.2 * arrow_size, draggable=True) + Manipulator.__init__(self, context, o, datablock, manipulator, snap_callback) + + def check_hover(self): + self.handle.check_hover(self.mouse_pos) + + def mouse_press(self, context, event): + if self.handle.hover: + self.handle.hover = False + self.handle.active = True + self.o.select = True + # takeloc = self.o.matrix_world * self.manipulator.p0 + # print("Invoke sp_point_move %s" % (takeloc)) + # @TODO: + # implement and add draw and callbacks + # snap_point(takeloc, draw, callback) + return True + return False + + def mouse_release(self, context, event): + self.check_hover() + self.handle.active = False + # False to callback manipulable_release + return False + + def update(self, context, event): + # NOTE: + # dosent set anything internally + return + + def mouse_move(self, context, event): + """ + + """ + self.mouse_position(event) + if self.handle.active: + # self.handle.active = np_snap.is_running + # self.update(context) + # True here to callback manipulable_manipulate + return True + else: + self.check_hover() + return False + + def draw_callback(self, _self, context, render=False): + left, right, side, normal = self.manipulator.get_pts(self.o.matrix_world) + self.handle.set_pos(context, left, Vector((1, 0, 0)), normal=normal) + self.handle.draw(context, render) + + +# Generic snap tool for line based archipack objects (fence, wall, maybe stair too) +gl_pts3d = [] + + +class WallSnapManipulator(Manipulator): + """ + np_station snap inspired manipulator + Use prop1_name as string part index + Use prop2_name as string identifier height property for placeholders + + Misnamed as it work for all line based archipack's + primitives, currently wall and fences, + but may also work with stairs (sharing same data structure) + """ + def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None): + self.placeholder_area = GlPolygon((0.5, 0, 0, 0.2)) + self.placeholder_line = GlPolyline((0.5, 0, 0, 0.8)) + self.placeholder_line.closed = True + self.label = GlText() + self.line = GlLine() + self.handle = SquareHandle(handle_size, 1.2 * arrow_size, draggable=True, selectable=True) + Manipulator.__init__(self, context, o, datablock, manipulator, snap_callback) + self.selectable = True + + def select(self, cursor_area): + self.selected = self.selected or cursor_area.in_area(self.handle.pos_2d) + self.handle.selected = self.selected + + def deselect(self, cursor_area): + self.selected = not cursor_area.in_area(self.handle.pos_2d) + self.handle.selected = self.selected + + def check_hover(self): + self.handle.check_hover(self.mouse_pos) + + def mouse_press(self, context, event): + global gl_pts3d + global manips + if self.handle.hover: + self.active = True + self.handle.active = True + gl_pts3d = [] + idx = int(self.manipulator.prop1_name) + + # get selected manipulators idx + selection = [] + for m in manips[self.o.name].stack: + if m is not None and m.selected: + selection.append(int(m.manipulator.prop1_name)) + + # store all points of wall + for i, part in enumerate(self.datablock.parts): + p0, p1, side, normal = part.manipulators[2].get_pts(self.o.matrix_world) + # if selected p0 will move and require placeholder + gl_pts3d.append((p0, p1, i in selection or i == idx)) + + self.feedback.instructions(context, "Move / Snap", "Drag to move, use keyboard to input values", [ + ('CTRL', 'Snap'), + ('X Y', 'Constraint to axis (toggle Global Local None)'), + ('SHIFT+Z', 'Constraint to xy plane'), + ('MMBTN', 'Constraint to axis'), + ('RIGHTCLICK or ESC', 'exit without change') + ]) + self.feedback.enable() + self.handle.hover = False + self.o.select = True + takeloc, right, side, dz = self.manipulator.get_pts(self.o.matrix_world) + dx = (right - takeloc).normalized() + dy = dz.cross(dx) + takemat = Matrix([ + [dx.x, dy.x, dz.x, takeloc.x], + [dx.y, dy.y, dz.y, takeloc.y], + [dx.z, dy.z, dz.z, takeloc.z], + [0, 0, 0, 1] + ]) + snap_point(takemat=takemat, draw=self.sp_draw, callback=self.sp_callback, + constraint_axis=(True, True, False)) + # this prevent other selected to run + return True + + return False + + def mouse_release(self, context, event): + self.check_hover() + self.handle.active = False + self.active = False + self.feedback.disable() + # False to callback manipulable_release + return False + + def sp_callback(self, context, event, state, sp): + """ + np station callback on moving, place, or cancel + """ + global gl_pts3d + + if state == 'SUCCESS': + + self.o.select = True + # apply changes to wall + d = self.datablock + d.auto_update = False + + g = d.get_generator() + + # rotation relative to object + rM = self.o.matrix_world.inverted().to_3x3() + delta = (rM * sp.delta).to_2d() + # x_axis = (rM * Vector((1, 0, 0))).to_2d() + + # update generator + idx = 0 + for p0, p1, selected in gl_pts3d: + + if selected: + + # new location in object space + pt = g.segs[idx].lerp(0) + delta + + # move last point of segment before current + if idx > 0: + g.segs[idx - 1].p1 = pt + + # move first point of current segment + g.segs[idx].p0 = pt + + idx += 1 + + # update properties from generator + idx = 0 + for p0, p1, selected in gl_pts3d: + + if selected: + + # adjust segment before current + if idx > 0: + w = g.segs[idx - 1] + part = d.parts[idx - 1] + + if idx > 1: + part.a0 = w.delta_angle(g.segs[idx - 2]) + else: + part.a0 = w.straight(1, 0).angle + + if "C_" in part.type: + part.radius = w.r + else: + part.length = w.length + + # adjust current segment + w = g.segs[idx] + part = d.parts[idx] + + if idx > 0: + part.a0 = w.delta_angle(g.segs[idx - 1]) + else: + part.a0 = w.straight(1, 0).angle + # move object when point 0 + self.o.location += sp.delta + + if "C_" in part.type: + part.radius = w.r + else: + part.length = w.length + + # adjust next one + if idx + 1 < d.n_parts: + d.parts[idx + 1].a0 = g.segs[idx + 1].delta_angle(w) + + idx += 1 + + self.mouse_release(context, event) + d.auto_update = True + + if state == 'CANCEL': + self.mouse_release(context, event) + + return + + def sp_draw(self, sp, context): + # draw wall placeholders + + global gl_pts3d + + if self.o is None: + return + + z = self.get_value(self.datablock, self.manipulator.prop2_name) + + placeholders = [] + for p0, p1, selected in gl_pts3d: + pt = p0.copy() + if selected: + # when selected, p0 is moving + # last one p1 should move too + # last one require a placeholder too + pt += sp.delta + if len(placeholders) > 0: + placeholders[-1][1] = pt + placeholders[-1][2] = True + placeholders.append([pt, p1, selected]) + + # first selected and closed -> should move last p1 too + if gl_pts3d[0][2] and self.datablock.closed: + placeholders[-1][1] = placeholders[0][0].copy() + placeholders[-1][2] = True + + # last one not visible when not closed + if not self.datablock.closed: + placeholders[-1][2] = False + + for p0, p1, selected in placeholders: + if selected: + self.placeholder_area.set_pos([p0, p1, Vector((p1.x, p1.y, p1.z + z)), Vector((p0.x, p0.y, p0.z + z))]) + self.placeholder_line.set_pos([p0, p1, Vector((p1.x, p1.y, p1.z + z)), Vector((p0.x, p0.y, p0.z + z))]) + self.placeholder_area.draw(context, render=False) + self.placeholder_line.draw(context, render=False) + + p0, p1, side, normal = self.manipulator.get_pts(self.o.matrix_world) + self.line.p = p0 + self.line.v = sp.delta + self.label.set_pos(context, self.line.length, self.line.lerp(0.5), self.line.v, normal=Vector((0, 0, 1))) + self.line.draw(context, render=False) + self.label.draw(context, render=False) + + def mouse_move(self, context, event): + self.mouse_position(event) + if self.handle.active: + # False here to pass_through + # print("i'm able to pick up mouse move event while transform running") + return False + else: + self.check_hover() + return False + + def draw_callback(self, _self, context, render=False): + left, right, side, normal = self.manipulator.get_pts(self.o.matrix_world) + self.handle.set_pos(context, left, (left - right).normalized(), normal=normal) + self.handle.draw(context, render) + self.feedback.draw(context, render) + + +class CounterManipulator(Manipulator): + """ + increase or decrease an integer step by step + right on click to prevent misuse + """ + def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None): + self.handle_left = TriHandle(handle_size, arrow_size, draggable=True) + self.handle_right = TriHandle(handle_size, arrow_size, draggable=True) + self.line_0 = GlLine() + self.label = GlText() + self.label.unit_mode = 'NONE' + self.label.precision = 0 + Manipulator.__init__(self, context, o, datablock, manipulator, snap_callback) + + def check_hover(self): + self.handle_right.check_hover(self.mouse_pos) + self.handle_left.check_hover(self.mouse_pos) + + def mouse_press(self, context, event): + if self.handle_right.hover: + value = self.get_value(self.datablock, self.manipulator.prop1_name) + self.set_value(context, self.datablock, self.manipulator.prop1_name, value + 1) + self.handle_right.active = True + return True + if self.handle_left.hover: + value = self.get_value(self.datablock, self.manipulator.prop1_name) + self.set_value(context, self.datablock, self.manipulator.prop1_name, value - 1) + self.handle_left.active = True + return True + return False + + def mouse_release(self, context, event): + self.check_hover() + self.handle_right.active = False + self.handle_left.active = False + return False + + def mouse_move(self, context, event): + self.mouse_position(event) + if self.handle_right.active: + return True + if self.handle_left.active: + return True + else: + self.check_hover() + return False + + def draw_callback(self, _self, context, render=False): + """ + draw on screen feedback using gl. + """ + # won't render counter + if render: + return + left, right, side, normal = self.manipulator.get_pts(self.o.matrix_world) + self.origin = left + self.line_0.p = left + self.line_0.v = right - left + self.line_0.z_axis = normal + self.label.z_axis = normal + value = self.get_value(self.datablock, self.manipulator.prop1_name) + self.handle_left.set_pos(context, self.line_0.p, -self.line_0.v, normal=normal) + self.handle_right.set_pos(context, self.line_0.lerp(1), self.line_0.v, normal=normal) + self.label.set_pos(context, value, self.line_0.lerp(0.5), self.line_0.v, normal=normal) + self.label.draw(context, render) + self.handle_left.draw(context, render) + self.handle_right.draw(context, render) + + +class DumbStringManipulator(Manipulator): + """ + not a real manipulator, but allow to show a string + """ + def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None): + self.label = GlText(colour=(0, 0, 0, 1)) + self.label.unit_mode = 'NONE' + self.label.label = manipulator.prop1_name + Manipulator.__init__(self, context, o, datablock, manipulator, snap_callback) + + def check_hover(self): + return False + + def mouse_press(self, context, event): + return False + + def mouse_release(self, context, event): + return False + + def mouse_move(self, context, event): + return False + + def draw_callback(self, _self, context, render=False): + """ + draw on screen feedback using gl. + """ + # won't render string + if render: + return + left, right, side, normal = self.manipulator.get_pts(self.o.matrix_world) + pos = left + 0.5 * (right - left) + self.label.set_pos(context, None, pos, pos, normal=normal) + self.label.draw(context, render) + + +class SizeManipulator(Manipulator): + + def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None): + self.handle_left = TriHandle(handle_size, arrow_size) + self.handle_right = TriHandle(handle_size, arrow_size, draggable=True) + self.line_0 = GlLine() + self.line_1 = GlLine() + self.line_2 = GlLine() + self.label = EditableText(handle_size, arrow_size, draggable=True) + # self.label.label = 'S ' + Manipulator.__init__(self, context, o, datablock, manipulator, snap_callback) + + def check_hover(self): + self.handle_right.check_hover(self.mouse_pos) + self.label.check_hover(self.mouse_pos) + + def mouse_press(self, context, event): + global gl_pts3d + if self.handle_right.hover: + self.active = True + self.original_size = self.get_value(self.datablock, self.manipulator.prop1_name) + self.original_location = self.o.matrix_world.translation.copy() + self.feedback.instructions(context, "Size", "Drag or Keyboard to modify size", [ + ('CTRL', 'Snap'), + ('SHIFT', 'Round'), + ('RIGHTCLICK or ESC', 'cancel') + ]) + left, right, side, dz = self.manipulator.get_pts(self.o.matrix_world) + dx = (right - left).normalized() + dy = dz.cross(dx) + takemat = Matrix([ + [dx.x, dy.x, dz.x, right.x], + [dx.y, dy.y, dz.y, right.y], + [dx.z, dy.z, dz.z, right.z], + [0, 0, 0, 1] + ]) + gl_pts3d = [left, right] + snap_point(takemat=takemat, + draw=self.sp_draw, + callback=self.sp_callback, + constraint_axis=(True, False, False)) + self.handle_right.active = True + return True + if self.label.hover: + self.feedback.instructions(context, "Size", "Use keyboard to modify size", + [('ENTER', 'Validate'), ('RIGHTCLICK or ESC', 'cancel')]) + self.label.active = True + self.keyboard_input_active = True + return True + return False + + def mouse_release(self, context, event): + self.active = False + self.check_hover() + self.handle_right.active = False + if not self.keyboard_input_active: + self.feedback.disable() + return False + + def mouse_move(self, context, event): + self.mouse_position(event) + if self.active: + self.update(context, event) + return True + else: + self.check_hover() + return False + + def cancel(self, context, event): + if self.active: + self.mouse_release(context, event) + self.set_value(context, self.datablock, self.manipulator.prop1_name, self.original_size) + + def keyboard_done(self, context, event, value): + self.set_value(context, self.datablock, self.manipulator.prop1_name, value) + self.label.active = False + return True + + def keyboard_cancel(self, context, event): + self.label.active = False + return False + + def update(self, context, event): + # 0 1 2 + # |_____| + # + pt = self.get_pos3d(context) + pt, t = intersect_point_line(pt, self.line_0.p, self.line_2.p) + length = (self.line_0.p - pt).length + if event.alt: + length = round(length, 1) + self.set_value(context, self.datablock, self.manipulator.prop1_name, length) + + def draw_callback(self, _self, context, render=False): + """ + draw on screen feedback using gl. + """ + left, right, side, normal = self.manipulator.get_pts(self.o.matrix_world) + self.origin = left + self.line_1.p = left + self.line_1.v = right - left + self.line_0.z_axis = normal + self.line_1.z_axis = normal + self.line_2.z_axis = normal + self.label.z_axis = normal + self.line_0 = self.line_1.sized_normal(0, side.x * 1.1) + self.line_2 = self.line_1.sized_normal(1, side.x * 1.1) + self.line_1.offset(side.x * 1.0) + self.handle_left.set_pos(context, self.line_1.p, -self.line_1.v, normal=normal) + self.handle_right.set_pos(context, self.line_1.lerp(1), self.line_1.v, normal=normal) + if not self.keyboard_input_active: + self.label_value = self.line_1.length + self.label.set_pos(context, self.label_value, self.line_1.lerp(0.5), self.line_1.v, normal=normal) + self.line_0.draw(context, render) + self.line_1.draw(context, render) + self.line_2.draw(context, render) + self.handle_left.draw(context, render) + self.handle_right.draw(context, render) + self.label.draw(context, render) + self.feedback.draw(context, render) + + def sp_draw(self, sp, context): + global gl_pts3d + if self.o is None: + return + p0 = gl_pts3d[0].copy() + p1 = gl_pts3d[1].copy() + p1 += sp.delta + self.sp_update(context, p0, p1) + return + + def sp_callback(self, context, event, state, sp): + + if state == 'SUCCESS': + self.sp_draw(sp, context) + self.mouse_release(context, event) + + if state == 'CANCEL': + p0 = gl_pts3d[0].copy() + p1 = gl_pts3d[1].copy() + self.sp_update(context, p0, p1) + self.mouse_release(context, event) + + def sp_update(self, context, p0, p1): + length = (p0 - p1).length + self.set_value(context, self.datablock, self.manipulator.prop1_name, length) + + +class SizeLocationManipulator(SizeManipulator): + """ + Handle resizing by any of the boundaries + of objects with centered pivots + so when size change, object should move of the + half of the change in the direction of change. + + Also take care of moving linked objects too + Changing size is not necessary as link does + allredy handle this and childs panels are + updated by base object. + """ + def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None): + SizeManipulator.__init__(self, context, o, datablock, manipulator, handle_size, snap_callback) + self.handle_left.draggable = True + + def check_hover(self): + self.handle_right.check_hover(self.mouse_pos) + self.handle_left.check_hover(self.mouse_pos) + self.label.check_hover(self.mouse_pos) + + def mouse_press(self, context, event): + if self.handle_right.hover: + self.active = True + self.original_location = self.o.matrix_world.translation.copy() + self.original_size = self.get_value(self.datablock, self.manipulator.prop1_name) + self.feedback.instructions(context, "Size", "Drag to modify size", [ + ('ALT', 'Round value'), ('RIGHTCLICK or ESC', 'cancel') + ]) + self.handle_right.active = True + return True + if self.handle_left.hover: + self.active = True + self.original_location = self.o.matrix_world.translation.copy() + self.original_size = self.get_value(self.datablock, self.manipulator.prop1_name) + self.feedback.instructions(context, "Size", "Drag to modify size", [ + ('ALT', 'Round value'), ('RIGHTCLICK or ESC', 'cancel') + ]) + self.handle_left.active = True + return True + if self.label.hover: + self.feedback.instructions(context, "Size", "Use keyboard to modify size", + [('ENTER', 'Validate'), ('RIGHTCLICK or ESC', 'cancel')]) + self.label.active = True + self.keyboard_input_active = True + return True + return False + + def mouse_release(self, context, event): + self.active = False + self.check_hover() + self.handle_right.active = False + self.handle_left.active = False + if not self.keyboard_input_active: + self.feedback.disable() + return False + + def mouse_move(self, context, event): + self.mouse_position(event) + if self.handle_right.active or self.handle_left.active: + self.update(context, event) + return True + else: + self.check_hover() + return False + + def keyboard_done(self, context, event, value): + self.set_value(context, self.datablock, self.manipulator.prop1_name, value) + # self.move_linked(context, self.manipulator.prop2_name, dl) + self.label.active = False + self.feedback.disable() + return True + + def cancel(self, context, event): + if self.active: + self.mouse_release(context, event) + # must move back to original location + itM = self.o.matrix_world.inverted() + dl = self.get_value(itM * self.original_location, self.manipulator.prop2_name) + + self.move(context, self.manipulator.prop2_name, dl) + self.set_value(context, self.datablock, self.manipulator.prop1_name, self.original_size) + self.move_linked(context, self.manipulator.prop2_name, dl) + + def update(self, context, event): + # 0 1 2 + # |_____| + # + pt = self.get_pos3d(context) + pt, t = intersect_point_line(pt, self.line_0.p, self.line_2.p) + + len_0 = (pt - self.line_0.p).length + len_1 = (pt - self.line_2.p).length + + length = max(len_0, len_1) + + if event.alt: + length = round(length, 1) + + dl = length - self.line_1.length + + if len_0 > len_1: + dl = 0.5 * dl + else: + dl = -0.5 * dl + + self.move(context, self.manipulator.prop2_name, dl) + self.set_value(context, self.datablock, self.manipulator.prop1_name, length) + self.move_linked(context, self.manipulator.prop2_name, dl) + + +class SnapSizeLocationManipulator(SizeLocationManipulator): + """ + Snap aware extension of SizeLocationManipulator + Handle resizing by any of the boundaries + of objects with centered pivots + so when size change, object should move of the + half of the change in the direction of change. + + Also take care of moving linked objects too + Changing size is not necessary as link does + allredy handle this and childs panels are + updated by base object. + + + """ + def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None): + SizeLocationManipulator.__init__(self, context, o, datablock, manipulator, handle_size, snap_callback) + + def mouse_press(self, context, event): + global gl_pts3d + if self.handle_right.hover: + self.active = True + self.original_size = self.get_value(self.datablock, self.manipulator.prop1_name) + self.original_location = self.o.matrix_world.translation.copy() + self.feedback.instructions(context, "Size", "Drag or Keyboard to modify size", [ + ('CTRL', 'Snap'), + ('SHIFT', 'Round'), + ('RIGHTCLICK or ESC', 'cancel') + ]) + left, right, side, dz = self.manipulator.get_pts(self.o.matrix_world) + dx = (right - left).normalized() + dy = dz.cross(dx) + takemat = Matrix([ + [dx.x, dy.x, dz.x, right.x], + [dx.y, dy.y, dz.y, right.y], + [dx.z, dy.z, dz.z, right.z], + [0, 0, 0, 1] + ]) + gl_pts3d = [left, right] + snap_point(takemat=takemat, + draw=self.sp_draw, + callback=self.sp_callback, + constraint_axis=(True, False, False)) + + self.handle_right.active = True + return True + + if self.handle_left.hover: + self.active = True + self.original_size = self.get_value(self.datablock, self.manipulator.prop1_name) + self.original_location = self.o.matrix_world.translation.copy() + self.feedback.instructions(context, "Size", "Drag or Keyboard to modify size", [ + ('CTRL', 'Snap'), + ('SHIFT', 'Round'), + ('RIGHTCLICK or ESC', 'cancel') + ]) + left, right, side, dz = self.manipulator.get_pts(self.o.matrix_world) + dx = (left - right).normalized() + dy = dz.cross(dx) + takemat = Matrix([ + [dx.x, dy.x, dz.x, left.x], + [dx.y, dy.y, dz.y, left.y], + [dx.z, dy.z, dz.z, left.z], + [0, 0, 0, 1] + ]) + gl_pts3d = [left, right] + snap_point(takemat=takemat, + draw=self.sp_draw, + callback=self.sp_callback, + constraint_axis=(True, False, False)) + self.handle_left.active = True + return True + + if self.label.hover: + self.feedback.instructions(context, "Size", "Use keyboard to modify size", + [('ENTER', 'Validate'), ('RIGHTCLICK or ESC', 'cancel')]) + self.label.active = True + self.keyboard_input_active = True + return True + + return False + + def sp_draw(self, sp, context): + global gl_pts3d + if self.o is None: + return + p0 = gl_pts3d[0].copy() + p1 = gl_pts3d[1].copy() + if self.handle_right.active: + p1 += sp.delta + else: + p0 += sp.delta + self.sp_update(context, p0, p1) + + # snapping child objects may require base object update + # eg manipulating windows requiring wall update + if self.snap_callback is not None: + snap_helper = context.active_object + self.snap_callback(context, o=self.o, manipulator=self) + context.scene.objects.active = snap_helper + + return + + def sp_callback(self, context, event, state, sp): + + if state == 'SUCCESS': + self.sp_draw(sp, context) + self.mouse_release(context, event) + + if state == 'CANCEL': + p0 = gl_pts3d[0].copy() + p1 = gl_pts3d[1].copy() + self.sp_update(context, p0, p1) + self.mouse_release(context, event) + + def sp_update(self, context, p0, p1): + l0 = self.get_value(self.datablock, self.manipulator.prop1_name) + length = (p0 - p1).length + dp = length - l0 + if self.handle_left.active: + dp = -dp + dl = 0.5 * dp + self.move(context, self.manipulator.prop2_name, dl) + self.set_value(context, self.datablock, self.manipulator.prop1_name, length) + self.move_linked(context, self.manipulator.prop2_name, dl) + + +class DeltaLocationManipulator(SizeManipulator): + """ + Move a child window or door in wall segment + not limited to this by the way + """ + def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None): + SizeManipulator.__init__(self, context, o, datablock, manipulator, handle_size, snap_callback) + self.label.label = '' + self.feedback.instructions(context, "Move", "Drag to move", [ + ('CTRL', 'Snap'), + ('SHIFT', 'Round value'), + ('RIGHTCLICK or ESC', 'cancel') + ]) + + def check_hover(self): + self.handle_right.check_hover(self.mouse_pos) + + def mouse_press(self, context, event): + global gl_pts3d + if self.handle_right.hover: + self.original_location = self.o.matrix_world.translation.copy() + self.active = True + self.feedback.enable() + self.handle_right.active = True + + left, right, side, dz = self.manipulator.get_pts(self.o.matrix_world) + dp = (right - left) + dx = dp.normalized() + dy = dz.cross(dx) + p0 = left + 0.5 * dp + takemat = Matrix([ + [dx.x, dy.x, dz.x, p0.x], + [dx.y, dy.y, dz.y, p0.y], + [dx.z, dy.z, dz.z, p0.z], + [0, 0, 0, 1] + ]) + gl_pts3d = [p0] + snap_point(takemat=takemat, + draw=self.sp_draw, + callback=self.sp_callback, + constraint_axis=( + self.manipulator.prop1_name == 'x', + self.manipulator.prop1_name == 'y', + self.manipulator.prop1_name == 'z')) + return True + return False + + def mouse_release(self, context, event): + self.check_hover() + self.feedback.disable() + self.active = False + self.handle_right.active = False + return False + + def mouse_move(self, context, event): + self.mouse_position(event) + if self.handle_right.active: + # self.update(context, event) + return True + else: + self.check_hover() + return False + + def sp_draw(self, sp, context): + global gl_pts3d + if self.o is None: + return + p0 = gl_pts3d[0].copy() + p1 = p0 + sp.delta + itM = self.o.matrix_world.inverted() + dl = self.get_value(itM * p1, self.manipulator.prop1_name) + self.move(context, self.manipulator.prop1_name, dl) + + # snapping child objects may require base object update + # eg manipulating windows requiring wall update + if self.snap_callback is not None: + snap_helper = context.active_object + self.snap_callback(context, o=self.o, manipulator=self) + context.scene.objects.active = snap_helper + + return + + def sp_callback(self, context, event, state, sp): + + if state == 'SUCCESS': + self.sp_draw(sp, context) + self.mouse_release(context, event) + + if state == 'CANCEL': + self.cancel(context, event) + + def cancel(self, context, event): + if self.active: + self.mouse_release(context, event) + # must move back to original location + itM = self.o.matrix_world.inverted() + dl = self.get_value(itM * self.original_location, self.manipulator.prop1_name) + self.move(context, self.manipulator.prop1_name, dl) + + def draw_callback(self, _self, context, render=False): + """ + draw on screen feedback using gl. + """ + left, right, side, normal = self.manipulator.get_pts(self.o.matrix_world) + self.origin = left + self.line_1.p = left + self.line_1.v = right - left + self.line_1.z_axis = normal + self.handle_left.set_pos(context, self.line_1.lerp(0.5), -self.line_1.v, normal=normal) + self.handle_right.set_pos(context, self.line_1.lerp(0.5), self.line_1.v, normal=normal) + self.handle_left.draw(context, render) + self.handle_right.draw(context, render) + self.feedback.draw(context) + + +class DumbSizeManipulator(SizeManipulator): + """ + Show a size while not being editable + """ + def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None): + SizeManipulator.__init__(self, context, o, datablock, manipulator, handle_size, snap_callback) + self.handle_right.draggable = False + self.label.draggable = False + self.label.colour_inactive = (0, 0, 0, 1) + # self.label.label = 'Dumb ' + + def mouse_move(self, context, event): + return False + + +class AngleManipulator(Manipulator): + """ + NOTE: + There is a default shortcut to +5 and -5 on angles with left/right arrows + + Manipulate angle between segments + bound to [-pi, pi] + """ + + def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None): + # Angle + self.handle_right = TriHandle(handle_size, arrow_size, draggable=True) + self.handle_center = SquareHandle(handle_size, arrow_size) + self.arc = GlArc() + self.line_0 = GlLine() + self.line_1 = GlLine() + self.label_a = EditableText(handle_size, arrow_size, draggable=True) + self.label_a.unit_type = 'ANGLE' + Manipulator.__init__(self, context, o, datablock, manipulator, snap_callback) + self.pts_mode = 'RADIUS' + + def check_hover(self): + self.handle_right.check_hover(self.mouse_pos) + self.label_a.check_hover(self.mouse_pos) + + def mouse_press(self, context, event): + if self.handle_right.hover: + self.active = True + self.original_angle = self.get_value(self.datablock, self.manipulator.prop1_name) + self.feedback.instructions(context, "Angle", "Drag to modify angle", [ + ('SHIFT', 'Round value'), + ('RIGHTCLICK or ESC', 'cancel') + ]) + self.handle_right.active = True + return True + if self.label_a.hover: + self.feedback.instructions(context, "Angle (degree)", "Use keyboard to modify angle", + [('ENTER', 'validate'), + ('RIGHTCLICK or ESC', 'cancel')]) + self.value_type = 'ROTATION' + self.label_a.active = True + self.label_value = self.get_value(self.datablock, self.manipulator.prop1_name) + self.keyboard_input_active = True + return True + return False + + def mouse_release(self, context, event): + self.check_hover() + self.handle_right.active = False + self.active = False + return False + + def mouse_move(self, context, event): + self.mouse_position(event) + if self.active: + # print("AngleManipulator.mouse_move") + self.update(context, event) + return True + else: + self.check_hover() + return False + + def keyboard_done(self, context, event, value): + self.set_value(context, self.datablock, self.manipulator.prop1_name, value) + self.label_a.active = False + return True + + def keyboard_cancel(self, context, event): + self.label_a.active = False + return False + + def cancel(self, context, event): + if self.active: + self.mouse_release(context, event) + self.set_value(context, self.datablock, self.manipulator.prop1_name, self.original_angle) + + def update(self, context, event): + pt = self.get_pos3d(context) + c = self.arc.c + v = 2 * self.arc.r * (pt - c).normalized() + v0 = c - v + v1 = c + v + p0, p1 = intersect_line_sphere(v0, v1, c, self.arc.r) + if p0 is not None and p1 is not None: + + if (p1 - pt).length < (p0 - pt).length: + p0, p1 = p1, p0 + + v = p0 - self.arc.c + da = atan2(v.y, v.x) - self.line_0.angle + if da > pi: + da -= 2 * pi + if da < -pi: + da += 2 * pi + # from there pi > da > -pi + # print("a:%.4f da:%.4f a0:%.4f" % (atan2(v.y, v.x), da, self.line_0.angle)) + if da > pi: + da = pi + if da < -pi: + da = -pi + if event.shift: + da = round(da / pi * 180, 0) / 180 * pi + self.set_value(context, self.datablock, self.manipulator.prop1_name, da) + + def draw_callback(self, _self, context, render=False): + c, left, right, normal = self.manipulator.get_pts(self.o.matrix_world) + self.line_0.z_axis = normal + self.line_1.z_axis = normal + self.arc.z_axis = normal + self.label_a.z_axis = normal + self.origin = c + self.line_0.p = c + self.line_1.p = c + self.arc.c = c + self.line_0.v = left + self.line_0.v = -self.line_0.cross.normalized() + self.line_1.v = right + self.line_1.v = self.line_1.cross.normalized() + self.arc.a0 = self.line_0.angle + self.arc.da = self.get_value(self.datablock, self.manipulator.prop1_name) + self.arc.r = 1.0 + self.handle_right.set_pos(context, self.line_1.lerp(1), + self.line_1.sized_normal(1, -1 if self.arc.da > 0 else 1).v) + self.handle_center.set_pos(context, self.arc.c, -self.line_0.v) + label_value = self.arc.da + if self.keyboard_input_active: + label_value = self.label_value + self.label_a.set_pos(context, label_value, self.arc.lerp(0.5), -self.line_0.v) + self.arc.draw(context, render) + self.line_0.draw(context, render) + self.line_1.draw(context, render) + self.handle_right.draw(context, render) + self.handle_center.draw(context, render) + self.label_a.draw(context, render) + self.feedback.draw(context, render) + + +class DumbAngleManipulator(AngleManipulator): + def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None): + AngleManipulator.__init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None) + self.handle_right.draggable = False + self.label_a.draggable = False + + def draw_callback(self, _self, context, render=False): + c, left, right, normal = self.manipulator.get_pts(self.o.matrix_world) + self.line_0.z_axis = normal + self.line_1.z_axis = normal + self.arc.z_axis = normal + self.label_a.z_axis = normal + self.origin = c + self.line_0.p = c + self.line_1.p = c + self.arc.c = c + self.line_0.v = left + self.line_0.v = -self.line_0.cross.normalized() + self.line_1.v = right + self.line_1.v = self.line_1.cross.normalized() + self.arc.a0 = self.line_0.angle + self.arc.da = self.line_1.v.to_2d().angle_signed(self.line_0.v.to_2d()) + self.arc.r = 1.0 + self.handle_right.set_pos(context, self.line_1.lerp(1), + self.line_1.sized_normal(1, -1 if self.arc.da > 0 else 1).v) + self.handle_center.set_pos(context, self.arc.c, -self.line_0.v) + label_value = self.arc.da + self.label_a.set_pos(context, label_value, self.arc.lerp(0.5), -self.line_0.v) + self.arc.draw(context, render) + self.line_0.draw(context, render) + self.line_1.draw(context, render) + self.handle_right.draw(context, render) + self.handle_center.draw(context, render) + self.label_a.draw(context, render) + self.feedback.draw(context, render) + + +class ArcAngleManipulator(Manipulator): + """ + Manipulate angle of an arc + when angle < 0 the arc center is on the left part of the circle + when angle > 0 the arc center is on the right part of the circle + bound to [-pi, pi] + """ + + def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None): + + # Fixed + self.handle_left = SquareHandle(handle_size, arrow_size) + # Angle + self.handle_right = TriHandle(handle_size, arrow_size, draggable=True) + self.handle_center = SquareHandle(handle_size, arrow_size) + self.arc = GlArc() + self.line_0 = GlLine() + self.line_1 = GlLine() + self.label_a = EditableText(handle_size, arrow_size, draggable=True) + self.label_r = EditableText(handle_size, arrow_size, draggable=False) + self.label_a.unit_type = 'ANGLE' + Manipulator.__init__(self, context, o, datablock, manipulator, snap_callback) + self.pts_mode = 'RADIUS' + + def check_hover(self): + self.handle_right.check_hover(self.mouse_pos) + self.label_a.check_hover(self.mouse_pos) + + def mouse_press(self, context, event): + if self.handle_right.hover: + self.active = True + self.original_angle = self.get_value(self.datablock, self.manipulator.prop1_name) + self.feedback.instructions(context, "Angle (degree)", "Drag to modify angle", [ + ('SHIFT', 'Round value'), + ('RIGHTCLICK or ESC', 'cancel') + ]) + self.handle_right.active = True + return True + if self.label_a.hover: + self.feedback.instructions(context, "Angle (degree)", "Use keyboard to modify angle", + [('ENTER', 'validate'), + ('RIGHTCLICK or ESC', 'cancel')]) + self.value_type = 'ROTATION' + self.label_value = self.get_value(self.datablock, self.manipulator.prop1_name) + self.label_a.active = True + self.keyboard_input_active = True + return True + if self.label_r.hover: + self.feedback.instructions(context, "Radius", "Use keyboard to modify radius", + [('ENTER', 'validate'), + ('RIGHTCLICK or ESC', 'cancel')]) + self.value_type = 'LENGTH' + self.label_r.active = True + self.keyboard_input_active = True + return True + return False + + def mouse_release(self, context, event): + self.check_hover() + self.handle_right.active = False + self.active = False + return False + + def mouse_move(self, context, event): + self.mouse_position(event) + if self.handle_right.active: + self.update(context, event) + return True + else: + self.check_hover() + return False + + def keyboard_done(self, context, event, value): + self.set_value(context, self.datablock, self.manipulator.prop1_name, value) + self.label_a.active = False + self.label_r.active = False + return True + + def keyboard_cancel(self, context, event): + self.label_a.active = False + self.label_r.active = False + return False + + def cancel(self, context, event): + if self.active: + self.mouse_release(context, event) + self.set_value(context, self.datablock, self.manipulator.prop1_name, self.original_angle) + + def update(self, context, event): + + pt = self.get_pos3d(context) + c = self.arc.c + + v = 2 * self.arc.r * (pt - c).normalized() + v0 = c - v + v1 = c + v + p0, p1 = intersect_line_sphere(v0, v1, c, self.arc.r) + + if p0 is not None and p1 is not None: + # find nearest mouse intersection point + if (p1 - pt).length < (p0 - pt).length: + p0, p1 = p1, p0 + + v = p0 - self.arc.c + + s = self.arc.tangeant(0, 1) + res, d, t = s.point_sur_segment(pt) + if d > 0: + # right side + a = self.arc.sized_normal(0, self.arc.r).angle + else: + a = self.arc.sized_normal(0, -self.arc.r).angle + + da = atan2(v.y, v.x) - a + + # bottom side +- pi + if t < 0: + # right + if d > 0: + da = pi + else: + da = -pi + # top side bound to +- pi + else: + if da > pi: + da -= 2 * pi + if da < -pi: + da += 2 * pi + + if event.shift: + da = round(da / pi * 180, 0) / 180 * pi + self.set_value(context, self.datablock, self.manipulator.prop1_name, da) + + def draw_callback(self, _self, context, render=False): + # center : 3d points + # left : 3d vector pt-c + # right : 3d vector pt-c + c, left, right, normal = self.manipulator.get_pts(self.o.matrix_world) + self.line_0.z_axis = normal + self.line_1.z_axis = normal + self.arc.z_axis = normal + self.label_a.z_axis = normal + self.label_r.z_axis = normal + self.origin = c + self.line_0.p = c + self.line_1.p = c + self.arc.c = c + self.line_0.v = left + self.line_1.v = right + self.arc.a0 = self.line_0.angle + self.arc.da = self.get_value(self.datablock, self.manipulator.prop1_name) + self.arc.r = left.length + self.handle_left.set_pos(context, self.line_0.lerp(1), self.line_0.v) + self.handle_right.set_pos(context, self.line_1.lerp(1), + self.line_1.sized_normal(1, -1 if self.arc.da > 0 else 1).v) + self.handle_center.set_pos(context, self.arc.c, -self.line_0.v) + label_a_value = self.arc.da + label_r_value = self.arc.r + if self.keyboard_input_active: + if self.value_type == 'LENGTH': + label_r_value = self.label_value + else: + label_a_value = self.label_value + self.label_a.set_pos(context, label_a_value, self.arc.lerp(0.5), -self.line_0.v) + self.label_r.set_pos(context, label_r_value, self.line_0.lerp(0.5), self.line_0.v) + self.arc.draw(context, render) + self.line_0.draw(context, render) + self.line_1.draw(context, render) + self.handle_left.draw(context, render) + self.handle_right.draw(context, render) + self.handle_center.draw(context, render) + self.label_r.draw(context, render) + self.label_a.draw(context, render) + self.feedback.draw(context, render) + + +class ArcAngleRadiusManipulator(ArcAngleManipulator): + """ + Manipulate angle and radius of an arc + when angle < 0 the arc center is on the left part of the circle + when angle > 0 the arc center is on the right part of the circle + bound to [-pi, pi] + """ + + def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None): + ArcAngleManipulator.__init__(self, context, o, datablock, manipulator, handle_size, snap_callback) + self.handle_center = TriHandle(handle_size, arrow_size, draggable=True) + self.label_r.draggable = True + + def check_hover(self): + self.handle_right.check_hover(self.mouse_pos) + self.handle_center.check_hover(self.mouse_pos) + self.label_a.check_hover(self.mouse_pos) + self.label_r.check_hover(self.mouse_pos) + + def mouse_press(self, context, event): + if self.handle_right.hover: + self.active = True + self.original_angle = self.get_value(self.datablock, self.manipulator.prop1_name) + self.feedback.instructions(context, "Angle (degree)", "Drag to modify angle", [ + ('SHIFT', 'Round value'), + ('RIGHTCLICK or ESC', 'cancel') + ]) + self.handle_right.active = True + return True + if self.handle_center.hover: + self.active = True + self.original_radius = self.get_value(self.datablock, self.manipulator.prop2_name) + self.feedback.instructions(context, "Radius", "Drag to modify radius", [ + ('SHIFT', 'Round value'), + ('RIGHTCLICK or ESC', 'cancel') + ]) + self.handle_center.active = True + return True + if self.label_a.hover: + self.feedback.instructions(context, "Angle (degree)", "Use keyboard to modify angle", + [('ENTER', 'validate'), + ('RIGHTCLICK or ESC', 'cancel')]) + self.value_type = 'ROTATION' + self.label_value = self.get_value(self.datablock, self.manipulator.prop1_name) + self.label_a.active = True + self.keyboard_input_active = True + return True + if self.label_r.hover: + self.feedback.instructions(context, "Radius", "Use keyboard to modify radius", + [('ENTER', 'validate'), + ('RIGHTCLICK or ESC', 'cancel')]) + self.value_type = 'LENGTH' + self.label_r.active = True + self.keyboard_input_active = True + return True + return False + + def mouse_release(self, context, event): + self.check_hover() + self.active = False + self.handle_right.active = False + self.handle_center.active = False + return False + + def mouse_move(self, context, event): + self.mouse_position(event) + if self.handle_right.active: + self.update(context, event) + return True + elif self.handle_center.active: + self.update_radius(context, event) + return True + else: + self.check_hover() + return False + + def keyboard_done(self, context, event, value): + if self.value_type == 'LENGTH': + self.set_value(context, self.datablock, self.manipulator.prop2_name, value) + self.label_r.active = False + else: + self.set_value(context, self.datablock, self.manipulator.prop1_name, value) + self.label_a.active = False + return True + + def update_radius(self, context, event): + pt = self.get_pos3d(context) + c = self.arc.c + left = self.line_0.lerp(1) + p, t = intersect_point_line(pt, c, left) + radius = (left - p).length + if event.alt: + radius = round(radius, 1) + self.set_value(context, self.datablock, self.manipulator.prop2_name, radius) + + def cancel(self, context, event): + if self.handle_right.active: + self.mouse_release(context, event) + self.set_value(context, self.datablock, self.manipulator.prop1_name, self.original_angle) + if self.handle_center.active: + self.mouse_release(context, event) + self.set_value(context, self.datablock, self.manipulator.prop2_name, self.original_radius) + + +# ------------------------------------------------------------------ +# Define a single Manipulator Properties to store on object +# ------------------------------------------------------------------ + + +# Allow registering manipulators classes +manipulators_class_lookup = {} + + +def register_manipulator(type_key, manipulator_class): + if type_key in manipulators_class_lookup.keys(): + raise RuntimeError("Manipulator of type {} allready exists, unable to override".format(type_key)) + manipulators_class_lookup[type_key] = manipulator_class + + +class archipack_manipulator(PropertyGroup): + """ + A property group to add to manipulable objects + type_key: type of manipulator + prop1_name = the property name of object to modify + prop2_name = another property name of object to modify (eg: angle and radius) + p0, p1, p2 3d Vectors as base points to represent manipulators on screen + normal Vector normal of plane on with draw manipulator + """ + type_key = StringProperty(default='SIZE') + + # How 3d points are stored in manipulators ? + # SIZE = 2 absolute positionned and a scaling vector + # RADIUS = 1 absolute positionned (center) and 2 relatives (sides) + # POLYGON = 2 absolute positionned and a relative vector (for rect polygons) + + pts_mode = StringProperty(default='SIZE') + prop1_name = StringProperty() + prop2_name = StringProperty() + p0 = FloatVectorProperty(subtype='XYZ') + p1 = FloatVectorProperty(subtype='XYZ') + p2 = FloatVectorProperty(subtype='XYZ') + # allow orientation of manipulators by default on xy plane, + # but may be used to constrain heights on local object space + normal = FloatVectorProperty(subtype='XYZ', default=(0, 0, 1)) + + def set_pts(self, pts, normal=None): + """ + set 3d location of gl points (in object space) + pts: array of 3 vectors 3d + normal: optionnal vector 3d default to Z axis + """ + pts = [Vector(p) for p in pts] + self.p0, self.p1, self.p2 = pts + if normal is not None: + self.normal = Vector(normal) + + def get_pts(self, tM): + """ + convert points from local to world absolute + to draw them at the right place + tM : object's world matrix + """ + rM = tM.to_3x3() + if self.pts_mode in ['SIZE', 'POLYGON']: + return tM * self.p0, tM * self.p1, self.p2, rM * self.normal + else: + return tM * self.p0, rM * self.p1, rM * self.p2, rM * self.normal + + def get_prefs(self, context): + global __name__ + global arrow_size + global handle_size + try: + # retrieve addon name from imports + addon_name = __name__.split('.')[0] + prefs = context.user_preferences.addons[addon_name].preferences + arrow_size = prefs.arrow_size + handle_size = prefs.handle_size + except: + pass + + def setup(self, context, o, datablock, snap_callback=None): + """ + Factory return a manipulator object or None + o: object + datablock: datablock to modify + snap_callback: function call y + """ + + self.get_prefs(context) + + global manipulators_class_lookup + + if self.type_key not in manipulators_class_lookup.keys() or \ + not manipulators_class_lookup[self.type_key].poll(context): + # RuntimeError is overkill but may be enabled for debug purposes + # Silentely ignore allow skipping manipulators if / when deps as not meet + # manip stack will simply be filled with None objects + # raise RuntimeError("Manipulator of type {} not found".format(self.type_key)) + return None + + m = manipulators_class_lookup[self.type_key](context, o, datablock, self, handle_size, snap_callback) + # points storage model as described upside + self.pts_mode = m.pts_mode + return m + + +# ------------------------------------------------------------------ +# Define Manipulable to make a PropertyGroup manipulable +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_manipulate(Operator): + bl_idname = "archipack.manipulate" + bl_label = "Manipulate" + bl_description = "Manipulate" + bl_options = {'REGISTER', 'UNDO'} + + object_name = StringProperty(default="") + + @classmethod + def poll(self, context): + return context.active_object is not None + + def exit_selectmode(self, context, key): + """ + Hide select area on exit + """ + global manips + if key in manips.keys(): + if manips[key].manipulable is not None: + manips[key].manipulable.manipulable_exit_selectmode(context) + + def modal(self, context, event): + global manips + # Exit on stack change + # handle multiple object stack + # use object_name property to find manupulated object in stack + # select and make object active + # and exit when not found + if context.area is not None: + context.area.tag_redraw() + key = self.object_name + if check_stack(key): + self.exit_selectmode(context, key) + remove_manipulable(key) + # print("modal exit by check_stack(%s)" % (key)) + return {'FINISHED'} + + res = manips[key].manipulable.manipulable_modal(context, event) + + if 'FINISHED' in res: + self.exit_selectmode(context, key) + remove_manipulable(key) + # print("modal exit by {FINISHED}") + + return res + + def invoke(self, context, event): + if context.space_data.type == 'VIEW_3D': + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + else: + self.report({'WARNING'}, "Active space must be a View3d") + return {'CANCELLED'} + + +class ARCHIPACK_OT_disable_manipulate(Operator): + bl_idname = "archipack.disable_manipulate" + bl_label = "Disable Manipulate" + bl_description = "Disable any active manipulator" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return True + + def execute(self, context): + empty_stack() + return {'FINISHED'} + + +class Manipulable(): + """ + A class extending PropertyGroup to setup gl manipulators + Beware : prevent crash calling manipulable_disable() + before changing manipulated data structure + """ + manipulators = CollectionProperty( + type=archipack_manipulator, + # options={'SKIP_SAVE'}, + # options={'HIDDEN'}, + description="store 3d points to draw gl manipulators" + ) + manipulable_refresh = BoolProperty( + default=False, + options={'SKIP_SAVE'}, + description="Flag enable to rebuild manipulators when data model change" + ) + manipulate_mode = BoolProperty( + default=False, + options={'SKIP_SAVE'}, + description="Flag manipulation state so we are able to toggle" + ) + select_mode = BoolProperty( + default=False, + options={'SKIP_SAVE'}, + description="Flag select state so we are able to toggle" + ) + manipulable_selectable = BoolProperty( + default=False, + options={'SKIP_SAVE'}, + description="Flag make manipulators selectable" + ) + keymap = None + + # selectable manipulators + manipulable_area = GlCursorArea() + manipulable_start_point = Vector((0, 0)) + manipulable_end_point = Vector((0, 0)) + manipulable_draw_handler = None + + def setup_manipulators(self): + """ + Must implement manipulators creation + TODO: call from update and manipulable_setup + """ + raise NotImplementedError + + def manipulable_draw_callback(self, _self, context): + self.manipulable_area.draw(context) + + def manipulable_disable(self, context): + """ + disable gl draw handlers + """ + o = context.active_object + if o is not None: + self.manipulable_exit_selectmode(context) + remove_manipulable(o.name) + self.manip_stack = add_manipulable(o.name, self) + + self.manipulate_mode = False + self.select_mode = False + + def manipulable_exit_selectmode(self, context): + self.manipulable_area.disable() + self.select_mode = False + # remove select draw handler + if self.manipulable_draw_handler is not None: + bpy.types.SpaceView3D.draw_handler_remove( + self.manipulable_draw_handler, + 'WINDOW') + self.manipulable_draw_handler = None + + def manipulable_setup(self, context): + """ + TODO: Implement the setup part as per parent object basis + """ + self.manipulable_disable(context) + o = context.active_object + self.setup_manipulators() + for m in self.manipulators: + self.manip_stack.append(m.setup(context, o, self)) + + def _manipulable_invoke(self, context): + + object_name = context.active_object.name + + # store a reference to self for operators + add_manipulable(object_name, self) + + # copy context so manipulator always use + # invoke time context + ctx = context.copy() + + # take care of context switching + # when call from outside of 3d view + if context.space_data.type != 'VIEW_3D': + for window in bpy.context.window_manager.windows: + screen = window.screen + for area in screen.areas: + if area.type == 'VIEW_3D': + ctx['area'] = area + for region in area.regions: + if region.type == 'WINDOW': + ctx['region'] = region + break + if ctx is not None: + bpy.ops.archipack.manipulate(ctx, 'INVOKE_DEFAULT', object_name=object_name) + + def manipulable_invoke(self, context): + """ + call this in operator invoke() + NB: + if override dont forget to call: + _manipulable_invoke(context) + + """ + # print("manipulable_invoke self.manipulate_mode:%s" % (self.manipulate_mode)) + + if self.manipulate_mode: + self.manipulable_disable(context) + return False + # else: + # bpy.ops.archipack.disable_manipulate('INVOKE_DEFAULT') + + # self.manip_stack = [] + # kills other's manipulators + # self.manipulate_mode = True + self.manipulable_setup(context) + self.manipulate_mode = True + + self._manipulable_invoke(context) + + return True + + def manipulable_modal(self, context, event): + """ + call in operator modal() + should not be overriden + as it provide all needed + functionnality out of the box + """ + # setup again when manipulators type change + if self.manipulable_refresh: + # print("manipulable_refresh") + self.manipulable_refresh = False + self.manipulable_setup(context) + self.manipulate_mode = True + + if context.area is None: + self.manipulable_disable(context) + return {'FINISHED'} + + context.area.tag_redraw() + + if self.keymap is None: + self.keymap = Keymaps(context) + + if self.keymap.check(event, self.keymap.undo): + # user feedback on undo by disabling manipulators + self.manipulable_disable(context) + return {'FINISHED'} + + # clean up manipulator on delete + if self.keymap.check(event, self.keymap.delete): # {'X'}: + # @TODO: + # for doors and windows, seek and destroy holes object if any + # a dedicated delete method into those objects may be an option ? + # A type check is required any way we choose + # + # Time for a generic archipack's datablock getter / filter into utils + # + # May also be implemented into nearly hidden "reference point" + # to delete / duplicate / link duplicate / unlink of + # a complete set of wall, doors and windows at once + self.manipulable_disable(context) + + if bpy.ops.object.delete.poll(): + bpy.ops.object.delete('INVOKE_DEFAULT', use_global=False) + + return {'FINISHED'} + + """ + # handle keyborad for select mode + if self.select_mode: + if event.type in {'A'} and event.value == 'RELEASE': + return {'RUNNING_MODAL'} + """ + + for manipulator in self.manip_stack: + # manipulator should return false on left mouse release + # so proper release handler is called + # and return true to call manipulate when required + # print("manipulator:%s" % manipulator) + if manipulator is not None and manipulator.modal(context, event): + self.manipulable_manipulate(context, event, manipulator) + return {'RUNNING_MODAL'} + + # print("Manipulable %s %s" % (event.type, event.value)) + + # Manipulators are not active so check for selection + if event.type == 'LEFTMOUSE': + + # either we are starting select mode + # user press on area not over maniuplator + # Prevent 3 mouse emultation to select when alt pressed + if self.manipulable_selectable and event.value == 'PRESS' and not event.alt: + self.select_mode = True + self.manipulable_area.enable() + self.manipulable_start_point = Vector((event.mouse_region_x, event.mouse_region_y)) + self.manipulable_area.set_location( + context, + self.manipulable_start_point, + self.manipulable_start_point) + # add a select draw handler + args = (self, context) + self.manipulable_draw_handler = bpy.types.SpaceView3D.draw_handler_add( + self.manipulable_draw_callback, + args, + 'WINDOW', + 'POST_PIXEL') + # don't keep focus + # as this prevent click over ui + # return {'RUNNING_MODAL'} + + elif event.value == 'RELEASE': + if self.select_mode: + # confirm selection + + self.manipulable_exit_selectmode(context) + + # keep focus + # return {'RUNNING_MODAL'} + + else: + # allow manipulator action on release + for manipulator in self.manip_stack: + if manipulator is not None and manipulator.selectable: + manipulator.selected = False + self.manipulable_release(context) + + elif self.select_mode and event.type == 'MOUSEMOVE' and event.value == 'PRESS': + # update select area size + self.manipulable_end_point = Vector((event.mouse_region_x, event.mouse_region_y)) + self.manipulable_area.set_location( + context, + self.manipulable_start_point, + self.manipulable_end_point) + if event.shift: + # deselect + for i, manipulator in enumerate(self.manip_stack): + if manipulator is not None and manipulator.selectable: + manipulator.deselect(self.manipulable_area) + else: + # select / more + for i, manipulator in enumerate(self.manip_stack): + if manipulator is not None and manipulator.selectable: + manipulator.select(self.manipulable_area) + # keep focus to prevent left select mouse to actually move object + return {'RUNNING_MODAL'} + + # event.alt here to prevent 3 button mouse emulation exit while zooming + if event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS' and not event.alt: + self.manipulable_disable(context) + self.manipulable_exit(context) + return {'FINISHED'} + + return {'PASS_THROUGH'} + + # Callbacks + def manipulable_release(self, context): + """ + Override with action to do on mouse release + eg: big update + """ + return + + def manipulable_exit(self, context): + """ + Override with action to do when modal exit + """ + return + + def manipulable_manipulate(self, context, event, manipulator): + """ + Override with action to do when a handle is active (pressed and mousemove) + """ + return + + +@persistent +def cleanup(dummy=None): + empty_stack() + + +def register(): + # Register default manipulators + global manips + global manipulators_class_lookup + manipulators_class_lookup = {} + manips = {} + register_manipulator('SIZE', SizeManipulator) + register_manipulator('SIZE_LOC', SizeLocationManipulator) + register_manipulator('ANGLE', AngleManipulator) + register_manipulator('DUMB_ANGLE', DumbAngleManipulator) + register_manipulator('ARC_ANGLE_RADIUS', ArcAngleRadiusManipulator) + register_manipulator('COUNTER', CounterManipulator) + register_manipulator('DUMB_SIZE', DumbSizeManipulator) + register_manipulator('DELTA_LOC', DeltaLocationManipulator) + register_manipulator('DUMB_STRING', DumbStringManipulator) + + # snap aware size loc + register_manipulator('SNAP_SIZE_LOC', SnapSizeLocationManipulator) + # register_manipulator('SNAP_POINT', SnapPointManipulator) + # wall's line based object snap + register_manipulator('WALL_SNAP', WallSnapManipulator) + bpy.utils.register_class(ARCHIPACK_OT_manipulate) + bpy.utils.register_class(ARCHIPACK_OT_disable_manipulate) + bpy.utils.register_class(archipack_manipulator) + bpy.app.handlers.load_pre.append(cleanup) + + +def unregister(): + global manips + global manipulators_class_lookup + empty_stack() + del manips + manipulators_class_lookup.clear() + del manipulators_class_lookup + bpy.utils.unregister_class(ARCHIPACK_OT_manipulate) + bpy.utils.unregister_class(ARCHIPACK_OT_disable_manipulate) + bpy.utils.unregister_class(archipack_manipulator) + bpy.app.handlers.load_pre.remove(cleanup) diff --git a/archipack/archipack_object.py b/archipack/archipack_object.py new file mode 100644 index 000000000..18ae43e5e --- /dev/null +++ b/archipack/archipack_object.py @@ -0,0 +1,237 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +# noinspection PyUnresolvedReferences +import bpy +# noinspection PyUnresolvedReferences +from bpy.props import BoolProperty, StringProperty +from mathutils import Vector, Matrix +from mathutils.geometry import ( + intersect_line_plane + ) +from bpy_extras.view3d_utils import ( + region_2d_to_origin_3d, + region_2d_to_vector_3d + ) +from .materialutils import MaterialUtils + + +class ArchipackObject(): + """ + Shared property of archipack's objects PropertyGroup + provide basic support for copy to selected + and datablock access / filtering by object + """ + + def iskindof(self, o, typ): + """ + return true if object contains databloc of typ name + """ + return o.data is not None and typ in o.data + + @classmethod + def filter(cls, o): + """ + Filter object with this class in data + return + True when object contains this datablock + False otherwhise + usage: + class_name.filter(object) from outside world + self.__class__.filter(object) from instance + """ + try: + return cls.__name__ in o.data + except: + pass + return False + + @classmethod + def datablock(cls, o): + """ + Retrieve datablock from base object + return + datablock when found + None when not found + usage: + class_name.datablock(object) from outside world + self.__class__.datablock(object) from instance + """ + try: + return getattr(o.data, cls.__name__)[0] + except: + pass + return None + + def find_in_selection(self, context, auto_update=True): + """ + find witch selected object this datablock instance belongs to + store context to be able to restore after oops + provide support for "copy to selected" + return + object or None when instance not found in selected objects + """ + if auto_update is False: + return None + + active = context.active_object + selected = [o for o in context.selected_objects] + + for o in selected: + if self.__class__.datablock(o) == self: + self.previously_selected = selected + self.previously_active = active + return o + + return None + + def restore_context(self, context): + # restore context + bpy.ops.object.select_all(action="DESELECT") + + try: + for o in self.previously_selected: + o.select = True + except: + pass + + self.previously_active.select = True + context.scene.objects.active = self.previously_active + self.previously_selected = None + self.previously_active = None + + +class ArchipackCreateTool(): + """ + Shared property of archipack's create tool Operator + """ + auto_manipulate = BoolProperty( + name="Auto manipulate", + description="Enable object's manipulators after create", + options={'SKIP_SAVE'}, + default=True + ) + filepath = StringProperty( + options={'SKIP_SAVE'}, + name="Preset", + description="Full filename of python preset to load at create time", + default="" + ) + + @property + def archipack_category(self): + """ + return target object name from ARCHIPACK_OT_object_name + """ + return self.bl_idname[13:] + + def load_preset(self, d): + """ + Load python preset + preset: full filename.py with path + """ + d.auto_update = False + if self.filepath != "": + try: + # print("Archipack loading preset: %s" % d.auto_update) + bpy.ops.script.python_file_run(filepath=self.filepath) + # print("Archipack preset loaded auto_update: %s" % d.auto_update) + except: + print("Archipack unable to load preset file : %s" % (self.filepath)) + pass + d.auto_update = True + + def add_material(self, o): + try: + getattr(MaterialUtils, "add_" + self.archipack_category + "_materials")(o) + except: + print("Archipack MaterialUtils.add_%s_materials not found" % (self.archipack_category)) + pass + + def manipulate(self): + if self.auto_manipulate: + try: + op = getattr(bpy.ops.archipack, self.archipack_category + "_manipulate") + if op.poll(): + op('INVOKE_DEFAULT') + except: + print("Archipack bpy.ops.archipack.%s_manipulate not found" % (self.archipack_category)) + pass + + +class ArchpackDrawTool(): + """ + Draw tools + """ + def mouse_to_plane(self, context, event, origin=Vector((0, 0, 0)), normal=Vector((0, 0, 1))): + """ + convert mouse pos to 3d point over plane defined by origin and normal + """ + region = context.region + rv3d = context.region_data + co2d = (event.mouse_region_x, event.mouse_region_y) + view_vector_mouse = region_2d_to_vector_3d(region, rv3d, co2d) + ray_origin_mouse = region_2d_to_origin_3d(region, rv3d, co2d) + pt = intersect_line_plane(ray_origin_mouse, ray_origin_mouse + view_vector_mouse, + origin, normal, False) + # fix issue with parallel plane + if pt is None: + pt = intersect_line_plane(ray_origin_mouse, ray_origin_mouse + view_vector_mouse, + origin, view_vector_mouse, False) + return pt + + def mouse_to_scene_raycast(self, context, event): + """ + convert mouse pos to 3d point over plane defined by origin and normal + """ + region = context.region + rv3d = context.region_data + co2d = (event.mouse_region_x, event.mouse_region_y) + view_vector_mouse = region_2d_to_vector_3d(region, rv3d, co2d) + ray_origin_mouse = region_2d_to_origin_3d(region, rv3d, co2d) + res, pos, normal, face_index, object, matrix_world = context.scene.ray_cast( + ray_origin_mouse, + view_vector_mouse) + return res, pos, normal, face_index, object, matrix_world + + def mouse_hover_wall(self, context, event): + """ + convert mouse pos to matrix at bottom of surrounded wall, y oriented outside wall + """ + res, pt, y, i, o, tM = self.mouse_to_scene_raycast(context, event) + if res and o.data is not None and 'archipack_wall2' in o.data: + z = Vector((0, 0, 1)) + d = o.data.archipack_wall2[0] + y = -y + pt += (0.5 * d.width) * y.normalized() + x = y.cross(z) + return True, Matrix([ + [x.x, y.x, z.x, pt.x], + [x.y, y.y, z.y, pt.y], + [x.z, y.z, z.z, o.matrix_world.translation.z], + [0, 0, 0, 1] + ]), o, y + return False, Matrix(), None, Vector() diff --git a/archipack/archipack_polylib.py b/archipack/archipack_polylib.py new file mode 100644 index 000000000..886029ba9 --- /dev/null +++ b/archipack/archipack_polylib.py @@ -0,0 +1,2274 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- + +bl_info = { + 'name': 'PolyLib', + 'description': 'Polygons detection from unordered splines', + 'author': 's-leger', + 'license': 'GPL', + 'deps': 'shapely', + 'version': (1, 1), + 'blender': (2, 7, 8), + 'location': 'View3D > Tools > Polygons', + 'warning': '', + 'wiki_url': 'https://github.com/s-leger/blenderPolygons/wiki', + 'tracker_url': 'https://github.com/s-leger/blenderPolygons/issues', + 'link': 'https://github.com/s-leger/blenderPolygons', + 'support': 'COMMUNITY', + 'category': '3D View' + } + +import sys +import time +import bpy +import bgl +import numpy as np +from math import cos, sin, pi, atan2 +import bmesh + +# let shapely import raise ImportError when missing +import shapely.ops +import shapely.prepared +from shapely.geometry import Point as ShapelyPoint +from shapely.geometry import Polygon as ShapelyPolygon + +try: + import shapely.speedups + if shapely.speedups.available: + shapely.speedups.enable() +except: + pass + +from .bitarray import BitArray +from .pyqtree import _QuadTree +from mathutils import Vector, Matrix +from mathutils.geometry import intersect_line_plane, interpolate_bezier +from bpy_extras import view3d_utils +from bpy.types import Operator, PropertyGroup +from bpy.props import StringProperty, FloatProperty, PointerProperty, EnumProperty, IntProperty, BoolProperty +from bpy.app.handlers import persistent +from .materialutils import MaterialUtils +from .archipack_gl import ( + FeedbackPanel, + GlCursorFence, + GlCursorArea, + GlLine, + GlPolyline +) + +# module globals vars dict +vars_dict = { + # spacial tree for segments and points + 'seg_tree': None, + 'point_tree': None, + # keep track of shapely geometry selection sets + 'select_polygons': None, + 'select_lines': None, + 'select_points': None + } + + +# module constants +# precision 1e-4 = 0.1mm +EPSILON = 1.0e-4 +# Qtree params +MAX_ITEMS = 10 +MAX_DEPTH = 20 + + +class CoordSys(object): + """ + reference coordsys + world : matrix from local to world + invert: matrix from world to local + width, height: bonding region size + """ + def __init__(self, objs): + x = [] + y = [] + if len(objs) > 0: + if hasattr(objs[0], 'bound_box'): + for obj in objs: + pos = obj.location + x.append(obj.bound_box[0][0] + pos.x) + x.append(obj.bound_box[6][0] + pos.x) + y.append(obj.bound_box[0][1] + pos.y) + y.append(obj.bound_box[6][1] + pos.y) + elif hasattr(objs[0], 'bounds'): + for geom in objs: + x0, y0, x1, y1 = geom.bounds + x.append(x0) + x.append(x1) + y.append(y0) + y.append(y1) + else: + raise Exception("CoordSys require at least one object with bounds or bound_box property to initialize") + else: + raise Exception("CoordSys require at least one object to initialize bounds") + x0 = min(x) + y0 = min(y) + x1 = max(x) + y1 = max(y) + width, height = x1 - x0, y1 - y0 + midx, midy = x0 + width / 2.0, y0 + height / 2.0 + # reference coordsys bounding box center + self.world = Matrix([ + [1, 0, 0, midx], + [0, 1, 0, midy], + [0, 0, 1, 0], + [0, 0, 0, 1], + ]) + self.invert = self.world.inverted() + self.width = width + self.height = height + + +class Prolongement(): + """ intersection of two segments outside segment (projection) + c0 = extremite sur le segment courant + c1 = intersection point on oposite segment + id = oposite segment id + t = param t on oposite segment + d = distance from ends to segment + insert = do we need to insert the point on other segment + use id, c1 and t to insert segment slices + """ + def __init__(self, c0, c1, id, t, d): + self.length = c0.distance(c1) + self.c0 = c0 + self.c1 = c1 + self.id = id + self.t = t + self.d = d + + +class Point(): + + def __init__(self, co, precision=EPSILON): + self.users = 0 + self.co = tuple(co) + x, y, z = co + self.shapeIds = [] + self.bounds = (x - precision, y - precision, x + precision, y + precision) + + @property + def geom(self): + return ShapelyPoint(self.co) + + def vect(self, point): + """ vector from this point to another """ + return np.subtract(point.co, self.co) + + def distance(self, point): + """ euclidian distance between points """ + return np.linalg.norm(self.vect(point)) + + def add_user(self): + self.users += 1 + + +class Segment(): + + def __init__(self, c0, c1, extend=EPSILON): + + self.c0 = c0 + self.c1 = c1 + self._splits = [] + + self.available = True + # ensure uniqueness when merge + + self.opposite = False + # this seg has an opposite + + self.original = False + # source of opposite + + x0, y0, z0 = c0.co + x1, y1, z1 = c1.co + self.bounds = (min(x0, x1) - extend, min(y0, y1) - extend, max(x0, x1) + extend, max(y0, y1) + extend) + + @property + def splits(self): + return sorted(self._splits) + + @property + def vect(self): + """ vector c0-c1""" + return np.subtract(self.c1.co, self.c0.co) + + @property + def vect_2d(self): + v = self.vect + v[2] = 0 + return v + + def lerp(self, t): + return np.add(self.c0.co, np.multiply(t, self.vect)) + + def _point_sur_segment(self, point): + """ _point_sur_segment + point: Point + t: param t de l'intersection sur le segment courant + d: distance laterale perpendiculaire + """ + vect = self.vect + dp = point.vect(self.c0) + dl = np.linalg.norm(vect) + d = np.linalg.norm(np.cross(vect, dp)) / dl + t = -np.divide(np.dot(dp, vect), np.multiply(dl, dl)) + if d < EPSILON: + if t > 0 and t < 1: + self._append_splits((t, point)) + + def is_end(self, point): + return point == self.c0 or point == self.c1 + + def min_intersect_dist(self, t, point): + """ distance intersection extremite la plus proche + t: param t de l'intersection sur le segment courant + point: Point d'intersection + return d: distance + """ + if t > 0.5: + return self.c1.distance(point) + else: + return self.c0.distance(point) + + def intersect(self, segment): + """ point_sur_segment return + p: point d'intersection + u: param t de l'intersection sur le segment courant + v: param t de l'intersection sur le segment segment + """ + v2d = self.vect_2d + c2 = np.cross(segment.vect_2d, (0, 0, 1)) + d = np.dot(v2d, c2) + if d == 0: + # segments paralleles + segment._point_sur_segment(self.c0) + segment._point_sur_segment(self.c1) + self._point_sur_segment(segment.c0) + self._point_sur_segment(segment.c1) + return False, 0, 0, 0 + c1 = np.cross(v2d, (0, 0, 1)) + v3 = self.c0.vect(segment.c0) + v3[2] = 0.0 + u = np.dot(c2, v3) / d + v = np.dot(c1, v3) / d + co = self.lerp(u) + return True, co, u, v + + def _append_splits(self, split): + """ + append a unique split point + """ + if split not in self._splits: + self._splits.append(split) + + def slice(self, d, t, point): + if d > EPSILON: + if t > 0.5: + if point != self.c1: + self._append_splits((t, point)) + else: + if point != self.c0: + self._append_splits((t, point)) + + def add_user(self): + self.c0.add_user() + self.c1.add_user() + + def consume(self): + self.available = False + + +class Shape(): + """ + Ensure uniqueness and fix precision issues by design + implicit closed with last point + require point_tree and seg_tree + """ + + def __init__(self, points=[]): + """ + @vertex: list of coords + """ + self.available = True + # Ensure uniqueness of shape when merging + + self._segs = [] + # Shape segments + + self.shapeId = [] + # Id of shape in shapes to keep a track of shape parts when merging + + self._create_segments(points) + + def _create_segments(self, points): + global vars_dict + if vars_dict['seg_tree'] is None: + raise RuntimeError('Shape._create_segments() require spacial index ') + # skip null segments with unique points test + self._segs = list(vars_dict['seg_tree'].newSegment(points[v], points[v + 1]) + for v in range(len(points) - 1) if points[v] != points[v + 1]) + + @property + def coords(self): + coords = list(seg.c0.co for seg in self._segs) + coords.append(self.c1.co) + return coords + + @property + def points(self): + points = list(seg.c0 for seg in self._segs) + points.append(self.c1) + return points + + @property + def c0(self): + if not self.valid: + raise RuntimeError('Shape does not contains any segments') + return self._segs[0].c0 + + @property + def c1(self): + if not self.valid: + raise RuntimeError('Shape does not contains any segments') + return self._segs[-1].c1 + + @property + def nbsegs(self): + return len(self._segs) + + @property + def valid(self): + return self.nbsegs > 0 + + @property + def closed(self): + return self.valid and bool(self.c0 == self.c1) + + def merge(self, shape): + """ merge this shape with specified shape + shapes must share at least one vertex + """ + if not self.valid or not shape.valid: + raise RuntimeError('Trying to merge invalid shape') + if self.c1 == shape.c1 or self.c0 == shape.c0: + shape._reverse() + if self.c1 == shape.c0: + self._segs += shape._segs + elif shape.c1 == self.c0: + self._segs = shape._segs + self._segs + else: + # should never happen + raise RuntimeError("Shape merge failed {} {} {} {}".format( + id(self), id(shape), self.shapeId, shape.shapeId)) + + def _reverse(self): + """ + reverse vertex order + """ + points = self.points[::-1] + self._create_segments(points) + + def slice(self, shapes): + """ + slice shape into smaller parts at intersections + """ + if not self.valid: + raise RuntimeError('Cant slice invalid shape') + points = [] + for seg in self._segs: + if seg.available and not seg.original: + seg.consume() + points.append(seg.c0) + if seg.c1.users > 2: + points.append(seg.c1) + shape = Shape(points) + shapes.append(shape) + points = [] + if len(points) > 0: + points.append(self.c1) + shape = Shape(points) + shapes.append(shape) + + def add_points(self): + """ + add points from intersection data + """ + points = [] + if self.nbsegs > 0: + for seg in self._segs: + points.append(seg.c0) + for split in seg.splits: + points.append(split[1]) + points.append(self.c1) + self._create_segments(points) + + def set_users(self): + """ + add users on segments and points + """ + for seg in self._segs: + seg.add_user() + + def consume(self): + self.available = False + + +class Qtree(_QuadTree): + """ + The top spatial index to be created by the user. Once created it can be + populated with geographically placed members that can later be tested for + intersection with a user inputted geographic bounding box. + """ + def __init__(self, coordsys, extend=EPSILON, max_items=MAX_ITEMS, max_depth=MAX_DEPTH): + """ + objs may be blender objects or shapely geoms + extend: how much seek arround + """ + self._extend = extend + self._geoms = [] + + # store input coordsys + self.coordsys = coordsys + + super(Qtree, self).__init__(0, 0, coordsys.width, coordsys.height, max_items, max_depth) + + @property + def ngeoms(self): + return len(self._geoms) + + def build(self, geoms): + """ + Build a spacial index from shapely geoms + """ + t = time.time() + self._geoms = geoms + for i, geom in enumerate(geoms): + self._insert(i, geom.bounds) + print("Qtree.build() :%.2f seconds" % (time.time() - t)) + + def insert(self, id, geom): + self._geoms.append(geom) + self._insert(id, geom.bounds) + + def newPoint(self, co): + point = Point(co, self._extend) + count, found = self.intersects(point) + for id in found: + return self._geoms[id] + self.insert(self.ngeoms, point) + return point + + def newSegment(self, c0, c1): + """ + allow "opposite" segments, + those segments are not found by intersects + and not stored in self.geoms + """ + new_seg = Segment(c0, c1, self._extend) + count, found = self.intersects(new_seg) + for id in found: + old_seg = self._geoms[id] + if (old_seg.c0 == c0 and old_seg.c1 == c1): + return old_seg + if (old_seg.c0 == c1 and old_seg.c1 == c0): + if not old_seg.opposite: + old_seg.opposite = new_seg + new_seg.original = old_seg + return old_seg.opposite + self.insert(self.ngeoms, new_seg) + return new_seg + + def intersects(self, geom): + selection = list(self._intersect(geom.bounds)) + count = len(selection) + return count, sorted(selection) + + +class Io(): + + @staticmethod + def ensure_iterable(obj): + try: + iter(obj) + except TypeError: + obj = [obj] + return obj + + # Conversion methods + @staticmethod + def _to_geom(shape): + if not shape.valid: + raise RuntimeError('Cant convert invalid shape to Shapely LineString') + return shapely.geometry.LineString(shape.coords) + + @staticmethod + def shapes_to_geoms(shapes): + return [Io._to_geom(shape) for shape in shapes] + + @staticmethod + def _to_shape(geometry, shapes): + global vars_dict + if vars_dict['point_tree'] is None: + raise RuntimeError("geoms to shapes require a global point_tree spacial index") + if hasattr(geometry, 'exterior'): + Io._to_shape(geometry.exterior, shapes) + for geom in geometry.interiors: + Io._to_shape(geom, shapes) + elif hasattr(geometry, 'geoms'): + # Multi and Collections + for geom in geometry.geoms: + Io._to_shape(geom, shapes) + else: + points = list(vars_dict['point_tree'].newPoint(p) for p in list(geometry.coords)) + shape = Shape(points) + shapes.append(shape) + + @staticmethod + def geoms_to_shapes(geoms, shapes=[]): + for geom in geoms: + Io._to_shape(geom, shapes) + return shapes + + # Input methods + @staticmethod + def _interpolate_bezier(pts, wM, p0, p1, resolution): + # straight segment, worth testing here + # since this can lower points count by a resolution factor + # use normalized to handle non linear t + if resolution == 0: + pts.append(wM * p0.co.to_3d()) + else: + v = (p1.co - p0.co).normalized() + d1 = (p0.handle_right - p0.co).normalized() + d2 = (p1.co - p1.handle_left).normalized() + if d1 == v and d2 == v: + pts.append(wM * p0.co.to_3d()) + else: + seg = interpolate_bezier(wM * p0.co, + wM * p0.handle_right, + wM * p1.handle_left, + wM * p1.co, + resolution) + for i in range(resolution - 1): + pts.append(seg[i].to_3d()) + + @staticmethod + def _coords_from_spline(wM, resolution, spline): + pts = [] + if spline.type == 'POLY': + pts = [wM * p.co.to_3d() for p in spline.points] + if spline.use_cyclic_u: + pts.append(pts[0]) + elif spline.type == 'BEZIER': + points = spline.bezier_points + for i in range(1, len(points)): + p0 = points[i - 1] + p1 = points[i] + Io._interpolate_bezier(pts, wM, p0, p1, resolution) + pts.append(wM * points[-1].co) + if spline.use_cyclic_u: + p0 = points[-1] + p1 = points[0] + Io._interpolate_bezier(pts, wM, p0, p1, resolution) + pts.append(pts[0]) + return pts + + @staticmethod + def _add_geom_from_curve(curve, invert_world, resolution, geoms): + wM = invert_world * curve.matrix_world + for spline in curve.data.splines: + pts = Io._coords_from_spline(wM, resolution, spline) + geom = shapely.geometry.LineString(pts) + geoms.append(geom) + + @staticmethod + def curves_to_geoms(curves, resolution, geoms=[]): + """ + @curves : blender curves collection + Return coordsys for outputs + """ + curves = Io.ensure_iterable(curves) + coordsys = CoordSys(curves) + t = time.time() + for curve in curves: + Io._add_geom_from_curve(curve, coordsys.invert, resolution, geoms) + print("Io.curves_as_line() :%.2f seconds" % (time.time() - t)) + return coordsys + + @staticmethod + def _add_shape_from_curve(curve, invert_world, resolution, shapes): + global vars_dict + wM = invert_world * curve.matrix_world + for spline in curve.data.splines: + pts = Io._coords_from_spline(wM, resolution, spline) + pts = [vars_dict['point_tree'].newPoint(pt) for pt in pts] + shape = Shape(points=pts) + shapes.append(shape) + + @staticmethod + def curves_to_shapes(curves, coordsys, resolution, shapes=[]): + """ + @curves : blender curves collection + Return simple shapes + """ + curves = Io.ensure_iterable(curves) + t = time.time() + for curve in curves: + Io._add_shape_from_curve(curve, coordsys.invert, resolution, shapes) + print("Io.curves_to_shapes() :%.2f seconds" % (time.time() - t)) + + # Output methods + @staticmethod + def _poly_to_wall(scene, matrix_world, poly, height, name): + global vars_dict + curve = bpy.data.curves.new(name, type='CURVE') + curve.dimensions = "2D" + curve.fill_mode = 'BOTH' + curve.extrude = height + n_ext = len(poly.exterior.coords) + n_int = len(poly.interiors) + Io._add_spline(curve, poly.exterior) + for geom in poly.interiors: + Io._add_spline(curve, geom) + curve_obj = bpy.data.objects.new(name, curve) + curve_obj.matrix_world = matrix_world + scene.objects.link(curve_obj) + curve_obj.select = True + scene.objects.active = curve_obj + return n_ext, n_int, curve_obj + + @staticmethod + def wall_uv(me, bm): + + for face in bm.faces: + face.select = face.material_index > 0 + + bmesh.update_edit_mesh(me, True) + bpy.ops.uv.cube_project(scale_to_bounds=False, correct_aspect=True) + + for face in bm.faces: + face.select = face.material_index < 1 + + bmesh.update_edit_mesh(me, True) + bpy.ops.uv.smart_project(use_aspect=True, stretch_to_bounds=False) + + @staticmethod + def to_wall(scene, coordsys, geoms, height, name, walls=[]): + """ + use curve extrude as it does respect vertices number and is not removing doubles + so it is easy to set material index + cap faces are tri, sides faces are quads + """ + bpy.ops.object.select_all(action='DESELECT') + geoms = Io.ensure_iterable(geoms) + for poly in geoms: + if hasattr(poly, 'exterior'): + half_height = height / 2.0 + n_ext, n_int, obj = Io._poly_to_wall(scene, coordsys.world, poly, half_height, name) + bpy.ops.object.convert(target="MESH") + bpy.ops.object.mode_set(mode='EDIT') + me = obj.data + bm = bmesh.from_edit_mesh(me) + bm.verts.ensure_lookup_table() + bm.faces.ensure_lookup_table() + for v in bm.verts: + v.co.z += half_height + nfaces = 0 + for i, f in enumerate(bm.faces): + bm.faces[i].material_index = 2 + if len(f.verts) > 3: + nfaces = i + break + # walls without holes are inside + mat_index = 0 if n_int > 0 else 1 + for i in range(nfaces, nfaces + n_ext - 1): + bm.faces[i].material_index = mat_index + for i in range(nfaces + n_ext - 1, len(bm.faces)): + bm.faces[i].material_index = 1 + bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.003) + bmesh.update_edit_mesh(me, True) + Io.wall_uv(me, bm) + bpy.ops.mesh.dissolve_limited(angle_limit=0.00349066, delimit={'NORMAL'}) + bpy.ops.mesh.dissolve_degenerate() + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.shade_flat() + MaterialUtils.add_wall_materials(obj) + walls.append(obj) + return walls + + @staticmethod + def _add_spline(curve, geometry): + coords = list(geometry.coords) + spline = curve.splines.new('POLY') + spline.use_endpoint_u = False + spline.use_cyclic_u = coords[0] == coords[-1] + spline.points.add(len(coords) - 1) + for i, coord in enumerate(coords): + x, y, z = Vector(coord).to_3d() + spline.points[i].co = (x, y, z, 1) + + @staticmethod + def _as_spline(curve, geometry): + """ + add a spline into a blender curve + @curve : blender curve + """ + if hasattr(geometry, 'exterior'): + # Polygon + Io._add_spline(curve, geometry.exterior) + for geom in geometry.interiors: + Io._add_spline(curve, geom) + elif hasattr(geometry, 'geoms'): + # Multi and Collections + for geom in geometry.geoms: + Io._as_spline(curve, geom) + else: + # LinearRing, LineString and Shape + Io._add_spline(curve, geometry) + + @staticmethod + def to_curve(scene, coordsys, geoms, name, dimensions='3D'): + global vars_dict + t = time.time() + geoms = Io.ensure_iterable(geoms) + curve = bpy.data.curves.new(name, type='CURVE') + curve.dimensions = dimensions + for geom in geoms: + Io._as_spline(curve, geom) + curve_obj = bpy.data.objects.new(name, curve) + curve_obj.matrix_world = coordsys.world + scene.objects.link(curve_obj) + curve_obj.select = True + print("Io.to_curves() :%.2f seconds" % (time.time() - t)) + return curve_obj + + @staticmethod + def to_curves(scene, coordsys, geoms, name, dimensions='3D'): + geoms = Io.ensure_iterable(geoms) + return [Io.to_curve(scene, coordsys, geom, name, dimensions) for geom in geoms] + + +class ShapelyOps(): + + @staticmethod + def min_bounding_rect(geom): + """ min_bounding_rect + minimum area oriented bounding rect + """ + # Compute edges (x2-x1,y2-y1) + if geom.convex_hull.geom_type == 'Polygon': + hull_points_2d = [list(coord[0:2]) for coord in list(geom.convex_hull.exterior.coords)] + else: + hull_points_2d = [list(coord[0:2]) for coord in list(geom.convex_hull.coords)] + edges = np.zeros((len(hull_points_2d) - 1, 2)) + # empty 2 column array + for i in range(len(edges)): + edge_x = hull_points_2d[i + 1][0] - hull_points_2d[i][0] + edge_y = hull_points_2d[i + 1][1] - hull_points_2d[i][1] + edges[i] = [edge_x, edge_y] + # Calculate edge angles atan2(y/x) + edge_angles = np.zeros((len(edges))) # empty 1 column array + for i in range(len(edge_angles)): + edge_angles[i] = atan2(edges[i, 1], edges[i, 0]) + # Check for angles in 1st quadrant + for i in range(len(edge_angles)): + edge_angles[i] = abs(edge_angles[i] % (pi / 2)) # want strictly positive answers + # Remove duplicate angles + edge_angles = np.unique(edge_angles) + # Test each angle to find bounding box with smallest area + min_bbox = (0, sys.maxsize, 0, 0, 0, 0, 0, 0) # rot_angle, area, width, height, min_x, max_x, min_y, max_y + # print "Testing", len(edge_angles), "possible rotations for bounding box... \n" + for i in range(len(edge_angles)): + # Create rotation matrix to shift points to baseline + # R = [ cos(theta) , cos(theta-PI/2) + # cos(theta+PI/2) , cos(theta) ] + R = np.array([[cos(edge_angles[i]), cos(edge_angles[i] - (pi / 2))], + [cos(edge_angles[i] + (pi / 2)), cos(edge_angles[i])]]) + # Apply this rotation to convex hull points + rot_points = np.dot(R, np.transpose(hull_points_2d)) # 2x2 * 2xn + # Find min/max x,y points + min_x = np.nanmin(rot_points[0], axis=0) + max_x = np.nanmax(rot_points[0], axis=0) + min_y = np.nanmin(rot_points[1], axis=0) + max_y = np.nanmax(rot_points[1], axis=0) + # Calculate height/width/area of this bounding rectangle + width = max_x - min_x + height = max_y - min_y + area = width * height + # Store the smallest rect found first + if (area < min_bbox[1]): + min_bbox = (edge_angles[i], area, width, height, min_x, max_x, min_y, max_y) + # Re-create rotation matrix for smallest rect + angle = min_bbox[0] + R = np.array([[cos(angle), cos(angle - (pi / 2))], [cos(angle + (pi / 2)), cos(angle)]]) + # min/max x,y points are against baseline + min_x = min_bbox[4] + max_x = min_bbox[5] + min_y = min_bbox[6] + max_y = min_bbox[7] + # Calculate center point and project onto rotated frame + center_x = (min_x + max_x) / 2 + center_y = (min_y + max_y) / 2 + center_point = np.dot([center_x, center_y], R) + if min_bbox[2] > min_bbox[3]: + a = -cos(angle) + b = sin(angle) + w = min_bbox[2] / 2 + h = min_bbox[3] / 2 + else: + a = -cos(angle + (pi / 2)) + b = sin(angle + (pi / 2)) + w = min_bbox[3] / 2 + h = min_bbox[2] / 2 + tM = Matrix([[a, b, 0, center_point[0]], [-b, a, 0, center_point[1]], [0, 0, 1, 0], [0, 0, 0, 1]]) + l_pts = [Vector((-w, -h, 0)), Vector((-w, h, 0)), Vector((w, h, 0)), Vector((w, -h, 0))] + w_pts = [tM * pt for pt in l_pts] + return tM, 2 * w, 2 * h, l_pts, w_pts + + @staticmethod + def detect_polygons(geoms): + """ detect_polygons + """ + print("Ops.detect_polygons()") + t = time.time() + result, dangles, cuts, invalids = shapely.ops.polygonize_full(geoms) + print("Ops.detect_polygons() :%.2f seconds" % (time.time() - t)) + return result, dangles, cuts, invalids + + @staticmethod + def optimize(geoms, tolerance=0.001, preserve_topology=True): + """ optimize + """ + t = time.time() + geoms = Io.ensure_iterable(geoms) + optimized = [geom.simplify(tolerance, preserve_topology) for geom in geoms] + print("Ops.optimize() :%.2f seconds" % (time.time() - t)) + return optimized + + @staticmethod + def union(geoms): + """ union (shapely based) + cascaded union - may require snap before use to fix precision issues + use union2 for best performances + """ + t = time.time() + geoms = Io.ensure_iterable(geoms) + collection = shapely.geometry.GeometryCollection(geoms) + union = shapely.ops.cascaded_union(collection) + print("Ops.union() :%.2f seconds" % (time.time() - t)) + return union + + +class ShapeOps(): + + @staticmethod + def union(shapes, extend=0.001): + """ union2 (Shape based) + cascaded union + require point_tree and seg_tree + """ + split = ShapeOps.split(shapes, extend=extend) + union = ShapeOps.merge(split) + return union + + @staticmethod + def _intersection_point(d, t, point, seg): + if d > EPSILON: + return point + elif t > 0.5: + return seg.c1 + else: + return seg.c0 + + @staticmethod + def split(shapes, extend=0.01): + """ _split + detect intersections between segments and slice shapes according + is able to project segment ends on closest segment + require point_tree and seg_tree + """ + global vars_dict + t = time.time() + new_shapes = [] + segs = vars_dict['seg_tree']._geoms + nbsegs = len(segs) + it_start = [None for x in range(nbsegs)] + it_end = [None for x in range(nbsegs)] + for s, seg in enumerate(segs): + count, idx = vars_dict['seg_tree'].intersects(seg) + for id in idx: + if id > s: + intersect, co, u, v = seg.intersect(segs[id]) + if intersect: + point = vars_dict['point_tree'].newPoint(co) + du = seg.min_intersect_dist(u, point) + dv = segs[id].min_intersect_dist(v, point) + # point intersection sur segment id + pt = ShapeOps._intersection_point(dv, v, point, segs[id]) + # print("s:%s id:%s u:%7f v:%7f du:%7f dv:%7f" % (s, id, u, v, du, dv)) + if u <= 0: + # prolonge segment s c0 + if du < extend and not seg.is_end(pt): + it = Prolongement(seg.c0, pt, id, v, du) + last = it_start[s] + if last is None or last.length > it.length: + it_start[s] = it + elif u < 1: + # intersection sur segment s + seg.slice(du, u, pt) + else: + # prolonge segment s c1 + if du < extend and not seg.is_end(pt): + it = Prolongement(seg.c1, pt, id, v, du) + last = it_end[s] + if last is None or last.length > it.length: + it_end[s] = it + pt = ShapeOps._intersection_point(du, u, point, seg) + if v <= 0: + # prolonge segment id c0 + if dv < extend and not segs[id].is_end(pt): + it = Prolongement(segs[id].c0, pt, s, u, dv) + last = it_start[id] + if last is None or last.length > it.length: + it_start[id] = it + elif v < 1: + # intersection sur segment s + segs[id].slice(dv, v, pt) + else: + # prolonge segment s c1 + if dv < extend and not segs[id].is_end(pt): + it = Prolongement(segs[id].c1, pt, s, u, dv) + last = it_end[id] + if last is None or last.length > it.length: + it_end[id] = it + for it in it_start: + if it is not None: + # print("it_start[%s] id:%s t:%4f d:%4f" % (s, it.id, it.t, it.d) ) + if it.t > 0 and it.t < 1: + segs[it.id]._append_splits((it.t, it.c1)) + if it.d > EPSILON: + shape = Shape([it.c0, it.c1]) + shapes.append(shape) + for it in it_end: + if it is not None: + # print("it_end[%s] id:%s t:%4f d:%4f" % (s, it.id, it.t, it.d) ) + if it.t > 0 and it.t < 1: + segs[it.id]._append_splits((it.t, it.c1)) + if it.d > EPSILON: + shape = Shape([it.c0, it.c1]) + shapes.append(shape) + print("Ops.split() intersect :%.2f seconds" % (time.time() - t)) + t = time.time() + for shape in shapes: + shape.add_points() + for shape in shapes: + shape.set_users() + for shape in shapes: + if shape.valid: + shape.slice(new_shapes) + print("Ops.split() slice :%.2f seconds" % (time.time() - t)) + return new_shapes + + @staticmethod + def merge(shapes): + """ merge + merge shapes ends + reverse use seg_tree + does not need tree as all: + - set shape ids to end vertices + - traverse shapes looking for points with 2 shape ids + - merge different shapes according + """ + t = time.time() + merged = [] + for i, shape in enumerate(shapes): + shape.available = True + shape.shapeId = [i] + shape.c0.shapeIds = [] + shape.c1.shapeIds = [] + for i, shape in enumerate(shapes): + shape.c0.shapeIds.append(i) + shape.c1.shapeIds.append(i) + for i, shape in enumerate(shapes): + shapeIds = shape.c1.shapeIds + if len(shapeIds) == 2: + if shapeIds[0] in shape.shapeId: + s = shapeIds[1] + else: + s = shapeIds[0] + if shape != shapes[s]: + shape.merge(shapes[s]) + shape.shapeId += shapes[s].shapeId + for j in shape.shapeId: + shapes[j] = shape + shapeIds = shape.c0.shapeIds + if len(shapeIds) == 2: + if shapeIds[0] in shape.shapeId: + s = shapeIds[1] + else: + s = shapeIds[0] + if shape != shapes[s]: + shape.merge(shapes[s]) + shape.shapeId += shapes[s].shapeId + for j in shape.shapeId: + shapes[j] = shape + for shape in shapes: + if shape.available: + shape.consume() + merged.append(shape) + print("Ops.merge() :%.2f seconds" % (time.time() - t)) + return merged + + +class Selectable(object): + + """ selectable shapely geoms """ + def __init__(self, geoms, coordsys): + # selection sets (bitArray) + self.selections = [] + # selected objects on screen representation + self.curves = [] + # Rtree to speedup region selections + self.tree = Qtree(coordsys) + self.tree.build(geoms) + # BitArray ids of selected geoms + self.ba = BitArray(self.ngeoms) + # Material to represent selection on screen + self.mat = self.build_display_mat("Selected", + color=bpy.context.user_preferences.themes[0].view_3d.object_selected) + self.cursor_fence = GlCursorFence() + self.cursor_fence.enable() + self.cursor_area = GlCursorArea() + self.feedback = FeedbackPanel() + self.action = None + self.store_index = 0 + + @property + def coordsys(self): + return self.tree.coordsys + + @property + def geoms(self): + return self.tree._geoms + + @property + def ngeoms(self): + return self.tree.ngeoms + + @property + def nsets(self): + return len(self.selections) + + def build_display_mat(self, name, color=(0.2, 0.2, 0)): + mat = MaterialUtils.build_default_mat(name, color) + mat.use_object_color = True + mat.emit = 0.2 + mat.alpha = 0.2 + mat.game_settings.alpha_blend = 'ADD' + return mat + + def _unselect(self, selection): + t = time.time() + for i in selection: + self.ba.clear(i) + print("Selectable._unselect() :%.2f seconds" % (time.time() - t)) + + def _select(self, selection): + t = time.time() + for i in selection: + self.ba.set(i) + print("Selectable._select() :%.2f seconds" % (time.time() - t)) + + def _position_3d_from_coord(self, context, coord): + """return point in local input coordsys + """ + region = context.region + rv3d = context.region_data + view_vector_mouse = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord) + ray_origin_mouse = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord) + loc = intersect_line_plane(ray_origin_mouse, ray_origin_mouse + view_vector_mouse, + Vector((0, 0, 0)), Vector((0, 0, 1)), False) + x, y, z = self.coordsys.invert * loc + return Vector((x, y, z)) + + def _position_2d_from_coord(self, context, coord): + """ coord given in local input coordsys + """ + region = context.region + rv3d = context.region_data + loc = view3d_utils.location_3d_to_region_2d(region, rv3d, self.coordsys.world * coord) + x, y = loc + return Vector((x, y)) + + def _contains(self, context, coord, event): + t = time.time() + point = self._position_3d_from_coord(context, coord) + selection = [] + pt = ShapelyPoint(point) + prepared_pt = shapely.prepared.prep(pt) + count, gids = self.tree.intersects(pt) + selection = [i for i in gids if prepared_pt.intersects(self.geoms[i])] + print("Selectable._contains() :%.2f seconds" % (time.time() - t)) + if event.shift: + self._unselect(selection) + else: + self._select(selection) + self._draw(context) + + def _intersects(self, context, coord, event): + t = time.time() + c0 = self._position_3d_from_coord(context, coord) + c1 = self._position_3d_from_coord(context, (coord[0], event.mouse_region_y)) + c2 = self._position_3d_from_coord(context, (event.mouse_region_x, event.mouse_region_y)) + c3 = self._position_3d_from_coord(context, (event.mouse_region_x, coord[1])) + poly = ShapelyPolygon([c0, c1, c2, c3]) + prepared_poly = shapely.prepared.prep(poly) + count, gids = self.tree.intersects(poly) + if event.ctrl: + selection = [i for i in gids if prepared_poly.contains(self.geoms[i])] + else: + selection = [i for i in gids if prepared_poly.intersects(self.geoms[i])] + print("Selectable._intersects() :%.2f seconds" % (time.time() - t)) + if event.shift: + self._unselect(selection) + else: + self._select(selection) + self._draw(context) + + def _hide(self, context): + t = time.time() + if len(self.curves) > 0: + try: + for curve in self.curves: + data = curve.data + context.scene.objects.unlink(curve) + bpy.data.objects.remove(curve, do_unlink=True) + if data is None: + return + name = data.name + if bpy.data.curves.find(name) > - 1: + bpy.data.curves.remove(data, do_unlink=True) + except: + pass + self.curves = [] + print("Selectable._hide() :%.2f seconds" % (time.time() - t)) + + def _draw(self, context): + print("Selectable._draw() %s" % (self.coordsys.world)) + t = time.time() + self._hide(context) + selection = [self.geoms[i] for i in self.ba.list] + if len(selection) > 1000: + self.curves = [Io.to_curve(context.scene, self.coordsys, selection, 'selection', '3D')] + else: + self.curves = Io.to_curves(context.scene, self.coordsys, selection, 'selection', '2D') + for curve in self.curves: + curve.color = (1, 1, 0, 1) + if len(curve.data.materials) < 1: + curve.data.materials.append(self.mat) + curve.active_material = self.mat + curve.select = True + print("Selectable._draw() :%.2f seconds" % (time.time() - t)) + + def store(self): + self.selections.append(self.ba.copy) + self.store_index = self.nsets + + def recall(self): + if self.nsets > 0: + if self.store_index < 1: + self.store_index = self.nsets + self.store_index -= 1 + self.ba = self.selections[self.store_index].copy + + def select(self, context, coord, event): + if abs(event.mouse_region_x - coord[0]) > 2 and abs(event.mouse_region_y - coord[1]) > 2: + self._intersects(context, coord, event) + else: + self._contains(context, (event.mouse_region_x, event.mouse_region_y), event) + + def init(self, pick_tool, context, action): + raise NotImplementedError("Selectable must implement init(self, pick_tool, context, action)") + + def keyboard(self, context, event): + """ keyboard events modal handler """ + raise NotImplementedError("Selectable must implement keyboard(self, context, event)") + + def complete(self, context): + raise NotImplementedError("Selectable must implement complete(self, context)") + + def modal(self, context, event): + """ modal handler """ + raise NotImplementedError("Selectable must implement modal(self, context, event)") + + def draw_callback(self, _self, context): + """ a gl draw callback """ + raise NotImplementedError("Selectable must implement draw_callback(self, _self, context)") + + +class SelectPoints(Selectable): + + def __init__(self, shapes, coordsys): + geoms = [] + for shape in shapes: + if shape.valid: + for point in shape.points: + point.users = 1 + for shape in shapes: + if shape.valid: + for point in shape.points: + if point.users > 0: + point.users = 0 + geoms.append(point.geom) + super(SelectPoints, self).__init__(geoms, coordsys) + + def _draw(self, context): + """ override draw method """ + print("SelectPoints._draw()") + t = time.time() + self._hide(context) + selection = list(self.geoms[i] for i in self.ba.list) + geom = ShapelyOps.union(selection) + self.curves = [Io.to_curve(context.scene, self.coordsys, geom.convex_hull, 'selection', '3D')] + for curve in self.curves: + curve.color = (1, 1, 0, 1) + curve.select = True + print("SelectPoints._draw() :%.2f seconds" % (time.time() - t)) + + def init(self, pick_tool, context, action): + # Post selection actions + self.selectMode = True + self.object_location = None + self.startPoint = (0, 0) + self.endPoint = (0, 0) + self.drag = False + self.feedback.instructions(context, "Select Points", "Click & Drag to select points in area", [ + ('SHIFT', 'deselect'), + ('CTRL', 'contains'), + ('A', 'All'), + ('I', 'Inverse'), + ('F', 'Create line around selection'), + # ('W', 'Create window using selection'), + # ('D', 'Create door using selection'), + ('ALT+F', 'Create best fit rectangle'), + ('R', 'Retrieve selection'), + ('S', 'Store selection'), + ('ESC or RIGHTMOUSE', 'exit when done') + ]) + self.feedback.enable() + args = (self, context) + self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback, args, 'WINDOW', 'POST_PIXEL') + self.action = action + self._draw(context) + print("SelectPoints.init()") + + def complete(self, context): + self.feedback.disable() + self._hide(context) + + def keyboard(self, context, event): + if event.type in {'A'}: + if len(self.ba.list) > 0: + self.ba.none() + else: + self.ba.all() + elif event.type in {'I'}: + self.ba.reverse() + elif event.type in {'S'}: + self.store() + elif event.type in {'R'}: + self.recall() + elif event.type in {'F'}: + sel = [self.geoms[i] for i in self.ba.list] + if len(sel) > 0: + scene = context.scene + geom = ShapelyOps.union(sel) + if event.alt: + tM, w, h, l_pts, w_pts = ShapelyOps.min_bounding_rect(geom) + x0 = -w / 2.0 + y0 = -h / 2.0 + x1 = w / 2.0 + y1 = h / 2.0 + poly = shapely.geometry.LineString([(x0, y0, 0), (x1, y0, 0), (x1, y1, 0), + (x0, y1, 0), (x0, y0, 0)]) + result = Io.to_curve(scene, self.coordsys, poly, 'points') + result.matrix_world = self.coordsys.world * tM + scene.objects.active = result + else: + result = Io.to_curve(scene, self.coordsys, geom.convex_hull, 'points') + scene.objects.active = result + self.ba.none() + self.complete(context) + elif event.type in {'W'}: + sel = [self.geoms[i] for i in self.ba.list] + if len(sel) > 0: + scene = context.scene + geom = ShapelyOps.union(sel) + if event.alt: + tM, w, h, l_pts, w_pts = ShapelyOps.min_bounding_rect(geom) + x0 = -w / 2.0 + y0 = -h / 2.0 + x1 = w / 2.0 + y1 = h / 2.0 + poly = shapely.geometry.LineString([(x0, y0, 0), (x1, y0, 0), (x1, y1, 0), + (x0, y1, 0), (x0, y0, 0)]) + result = Io.to_curve(scene, self.coordsys, poly, 'points') + result.matrix_world = self.coordsys.world * tM + scene.objects.active = result + else: + result = Io.to_curve(scene, self.coordsys, geom.convex_hull, 'points') + scene.objects.active = result + self.ba.none() + self.complete(context) + elif event.type in {'D'}: + sel = [self.geoms[i] for i in self.ba.list] + if len(sel) > 0: + scene = context.scene + geom = ShapelyOps.union(sel) + if event.alt: + tM, w, h, l_pts, w_pts = ShapelyOps.min_bounding_rect(geom) + x0 = -w / 2.0 + y0 = -h / 2.0 + x1 = w / 2.0 + y1 = h / 2.0 + poly = shapely.geometry.LineString([(x0, y0, 0), (x1, y0, 0), (x1, y1, 0), + (x0, y1, 0), (x0, y0, 0)]) + result = Io.to_curve(scene, self.coordsys, poly, 'points') + result.matrix_world = self.coordsys.world * tM + scene.objects.active = result + else: + result = Io.to_curve(scene, self.coordsys, geom.convex_hull, 'points') + scene.objects.active = result + self.ba.none() + self.complete(context) + self._draw(context) + + def modal(self, context, event): + if event.type in {'I', 'A', 'S', 'R', 'F'} and event.value == 'PRESS': + self.keyboard(context, event) + elif event.type in {'RIGHTMOUSE', 'ESC'}: + self.complete(context) + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + return {'FINISHED'} + elif event.type == 'LEFTMOUSE' and event.value == 'PRESS': + self.drag = True + self.cursor_area.enable() + self.cursor_fence.disable() + self.startPoint = (event.mouse_region_x, event.mouse_region_y) + self.endPoint = (event.mouse_region_x, event.mouse_region_y) + elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE': + self.drag = False + self.cursor_area.disable() + self.cursor_fence.enable() + self.endPoint = (event.mouse_region_x, event.mouse_region_y) + self.select(context, self.startPoint, event) + elif event.type == 'MOUSEMOVE': + self.endPoint = (event.mouse_region_x, event.mouse_region_y) + return {'RUNNING_MODAL'} + + def draw_callback(self, _self, context): + self.feedback.draw(context) + self.cursor_area.set_location(context, self.startPoint, self.endPoint) + self.cursor_fence.set_location(context, self.endPoint) + self.cursor_area.draw(context) + self.cursor_fence.draw(context) + + +class SelectLines(Selectable): + + def __init__(self, geoms, coordsys): + super(SelectLines, self).__init__(geoms, coordsys) + + def _draw(self, context): + """ override draw method """ + print("SelectLines._draw()") + t = time.time() + self._hide(context) + selection = list(self.geoms[i] for i in self.ba.list) + self.curves = [Io.to_curve(context.scene, self.coordsys, selection, 'selection', '3D')] + for curve in self.curves: + curve.color = (1, 1, 0, 1) + curve.select = True + print("SelectLines._draw() :%.2f seconds" % (time.time() - t)) + + def init(self, pick_tool, context, action): + # Post selection actions + self.selectMode = True + self.object_location = None + self.startPoint = (0, 0) + self.endPoint = (0, 0) + self.drag = False + self.feedback.instructions(context, "Select Lines", "Click & Drag to select lines in area", [ + ('SHIFT', 'deselect'), + ('CTRL', 'contains'), + ('A', 'All'), + ('I', 'Inverse'), + # ('F', 'Create lines from selection'), + ('R', 'Retrieve selection'), + ('S', 'Store selection'), + ('ESC or RIGHTMOUSE', 'exit when done') + ]) + self.feedback.enable() + args = (self, context) + self._handle = bpy.types.SpaceView3D.draw_handler_add( + self.draw_callback, args, 'WINDOW', 'POST_PIXEL') + self.action = action + self._draw(context) + print("SelectLines.init()") + + def complete(self, context): + print("SelectLines.complete()") + t = time.time() + self._hide(context) + scene = context.scene + selection = list(self.geoms[i] for i in self.ba.list) + if len(selection) > 0: + if self.action == 'select': + result = Io.to_curve(scene, self.coordsys, selection, 'selection') + scene.objects.active = result + elif self.action == 'union': + shapes = Io.geoms_to_shapes(selection) + merged = ShapeOps.merge(shapes) + union = Io.shapes_to_geoms(merged) + # union = self.ops.union(selection) + resopt = ShapelyOps.optimize(union) + result = Io.to_curve(scene, self.coordsys, resopt, 'union') + scene.objects.active = result + self.feedback.disable() + print("SelectLines.complete() :%.2f seconds" % (time.time() - t)) + + def keyboard(self, context, event): + if event.type in {'A'}: + if len(self.ba.list) > 0: + self.ba.none() + else: + self.ba.all() + elif event.type in {'I'}: + self.ba.reverse() + elif event.type in {'S'}: + self.store() + elif event.type in {'R'}: + self.recall() + self._draw(context) + + def modal(self, context, event): + if event.type in {'I', 'A', 'S', 'R'} and event.value == 'PRESS': + self.keyboard(context, event) + elif event.type in {'RIGHTMOUSE', 'ESC'}: + self.complete(context) + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + return {'FINISHED'} + elif event.type == 'LEFTMOUSE' and event.value == 'PRESS': + self.drag = True + self.cursor_area.enable() + self.cursor_fence.disable() + self.startPoint = (event.mouse_region_x, event.mouse_region_y) + self.endPoint = (event.mouse_region_x, event.mouse_region_y) + elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE': + self.drag = False + self.cursor_area.disable() + self.cursor_fence.enable() + self.endPoint = (event.mouse_region_x, event.mouse_region_y) + self.select(context, self.startPoint, event) + elif event.type == 'MOUSEMOVE': + self.endPoint = (event.mouse_region_x, event.mouse_region_y) + return {'RUNNING_MODAL'} + + def draw_callback(self, _self, context): + self.feedback.draw(context) + self.cursor_area.set_location(context, self.startPoint, self.endPoint) + self.cursor_fence.set_location(context, self.endPoint) + self.cursor_area.draw(context) + self.cursor_fence.draw(context) + + +class SelectPolygons(Selectable): + + def __init__(self, geoms, coordsys): + super(SelectPolygons, self).__init__(geoms, coordsys) + + """ + pick_tools actions + """ + def init(self, pick_tool, context, action): + # Post selection actions + self.need_rotation = False + self.direction = 0 + self.object_location = None + self.selectMode = True + self.startPoint = (0, 0) + self.endPoint = (0, 0) + if action in ['select', 'union', 'rectangle']: + self.feedback.instructions(context, "Select Polygons", "Click & Drag to select polygons in area", [ + ('SHIFT', 'deselect'), + ('CTRL', 'contains'), + ('A', 'All'), + ('I', 'Inverse'), + ('B', 'Bigger than current'), + # ('F', 'Create from selection'), + ('R', 'Retrieve selection'), + ('S', 'Store selection'), + ('ESC or RIGHTMOUSE', 'exit when done') + ]) + elif action == 'wall': + self.feedback.instructions(context, "Select Polygons", "Click & Drag to select polygons in area", [ + ('SHIFT', 'deselect'), + ('CTRL', 'contains'), + ('A', 'All'), + ('I', 'Inverse'), + ('B', 'Bigger than current'), + ('R', 'Retrieve selection'), + ('S', 'Store selection'), + ('ESC or RIGHTMOUSE', 'exit and build wall when done') + ]) + elif action == 'window': + self.feedback.instructions(context, "Select Polygons", "Click & Drag to select polygons in area", [ + ('SHIFT', 'deselect'), + ('CTRL', 'contains'), + ('A', 'All'), + ('I', 'Inverse'), + ('B', 'Bigger than current'), + ('F', 'Create a window from selection'), + ('ESC or RIGHTMOUSE', 'exit tool when done') + ]) + elif action == 'door': + self.feedback.instructions(context, "Select Polygons", "Click & Drag to select polygons in area", [ + ('SHIFT', 'deselect'), + ('CTRL', 'contains'), + ('A', 'All'), + ('I', 'Inverse'), + ('B', 'Bigger than current'), + ('F', 'Create a door from selection'), + ('ESC or RIGHTMOUSE', 'exit tool when done') + ]) + self.gl_arc = GlPolyline((1.0, 1.0, 1.0, 0.5), d=3) + self.gl_arc.width = 1 + self.gl_arc.style = bgl.GL_LINE_STIPPLE + self.gl_line = GlLine(d=3) + self.gl_line.colour_inactive = (1.0, 1.0, 1.0, 0.5) + self.gl_line.width = 2 + self.gl_line.style = bgl.GL_LINE_STIPPLE + self.gl_side = GlLine(d=2) + self.gl_side.colour_inactive = (1.0, 1.0, 1.0, 0.5) + self.gl_side.width = 2 + self.gl_side.style = bgl.GL_LINE_STIPPLE + self.feedback.enable() + self.drag = False + args = (self, context) + self._handle = bpy.types.SpaceView3D.draw_handler_add( + self.draw_callback, args, 'WINDOW', 'POST_PIXEL') + self.action = action + self._draw(context) + print("SelectPolygons.init()") + + def complete(self, context): + print("SelectPolygons.complete()") + t = time.time() + scene = context.scene + self._hide(context) + selection = list(self.geoms[i] for i in self.ba.list) + if len(selection) > 0: + if self.action == 'select': + result = Io.to_curve(scene, self.coordsys, selection, 'selection') + scene.objects.active = result + elif self.action == 'union': + union = ShapelyOps.union(selection) + resopt = ShapelyOps.optimize(union) + result = Io.to_curve(scene, self.coordsys, resopt, 'union') + scene.objects.active = result + elif self.action == 'wall': + union = ShapelyOps.union(selection) + union = ShapelyOps.optimize(union) + res = [] + z = context.window_manager.archipack_polylib.solidify_thickness + Io.to_wall(scene, self.coordsys, union, z, 'wall', res) + if len(res) > 0: + scene.objects.active = res[0] + if len(res) > 1: + bpy.ops.object.join() + bpy.ops.archipack.wall(z=z) + elif self.action == 'rectangle': + # currently only output a best fitted rectangle + # over selection + if self.object_location is not None: + tM, w, h, l_pts, w_pts = self.object_location + poly = shapely.geometry.LineString(l_pts) + result = Io.to_curve(scene, self.coordsys, poly, 'rectangle') + result.matrix_world = self.coordsys.world * tM + scene.objects.active = result + self.ba.none() + elif self.action == 'window': + if self.object_location is not None: + + tM, w, h, l_pts, w_pts = self.object_location + + if self.need_rotation: + rM = Matrix([ + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + ]) + else: + rM = Matrix() + + if w > 1.8: + z = 2.2 + altitude = 0.0 + else: + z = 1.2 + altitude = 1.0 + + bpy.ops.archipack.window(x=w, y=h, z=z, altitude=altitude, auto_manipulate=False) + result = context.object + result.matrix_world = self.coordsys.world * tM * rM + result.data.archipack_window[0].hole_margin = 0.02 + self.ba.none() + elif self.action == 'door': + if self.object_location is not None: + + tM, w, h, l_pts, w_pts = self.object_location + + if self.need_rotation: + rM = Matrix([ + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + ]) + else: + rM = Matrix() + + if w < 1.5: + n_panels = 1 + else: + n_panels = 2 + + bpy.ops.archipack.door(x=w, y=h, z=2.0, n_panels=n_panels, + direction=self.direction, auto_manipulate=False) + result = context.object + result.matrix_world = self.coordsys.world * tM * rM + result.data.archipack_door[0].hole_margin = 0.02 + self.ba.none() + + if self.action not in ['window', 'door']: + self.feedback.disable() + + print("SelectPolygons.complete() :%.2f seconds" % (time.time() - t)) + + def keyboard(self, context, event): + if event.type in {'A'}: + if len(self.ba.list) > 0: + self.ba.none() + else: + self.ba.all() + elif event.type in {'I'}: + self.ba.reverse() + elif event.type in {'S'}: + self.store() + elif event.type in {'R'}: + self.recall() + elif event.type in {'B'}: + areas = [self.geoms[i].area for i in self.ba.list] + area = max(areas) + self.ba.none() + for i, geom in enumerate(self.geoms): + if geom.area > area: + self.ba.set(i) + elif event.type in {'F'}: + if self.action == 'rectangle': + self.complete(context) + else: + sel = [self.geoms[i] for i in self.ba.list] + if len(sel) > 0: + if self.action == 'window': + self.feedback.instructions(context, + "Select Polygons", "Click & Drag to select polygons in area", [ + ('CLICK & DRAG', 'Set window orientation'), + ('RELEASE', 'Create window'), + ('F', 'Return to select mode'), + ('ESC or RIGHTMOUSE', 'exit tool when done') + ]) + elif self.action == 'door': + self.feedback.instructions(context, + "Select Polygons", "Click & Drag to select polygons in area", [ + ('CLICK & DRAG', 'Set door orientation'), + ('RELEASE', 'Create door'), + ('F', 'Return to select mode'), + ('ESC or RIGHTMOUSE', 'exit tool when done') + ]) + self.selectMode = not self.selectMode + geom = ShapelyOps.union(sel) + tM, w, h, l_pts, w_pts = ShapelyOps.min_bounding_rect(geom) + self.object_location = (tM, w, h, l_pts, w_pts) + self.startPoint = self._position_2d_from_coord(context, tM.translation) + self._draw(context) + + def modal(self, context, event): + if event.type in {'I', 'A', 'S', 'R', 'F', 'B'} and event.value == 'PRESS': + + self.keyboard(context, event) + elif event.type in {'RIGHTMOUSE', 'ESC'}: + if self.action == 'object': + self._hide(context) + else: + self.complete(context) + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + return {'FINISHED'} + elif event.type == 'LEFTMOUSE' and event.value == 'PRESS': + self.drag = True + self.cursor_area.enable() + self.cursor_fence.disable() + if self.selectMode: + self.startPoint = (event.mouse_region_x, event.mouse_region_y) + self.endPoint = (event.mouse_region_x, event.mouse_region_y) + elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE': + self.drag = False + self.cursor_area.disable() + self.cursor_fence.enable() + self.endPoint = (event.mouse_region_x, event.mouse_region_y) + if self.selectMode: + self.select(context, self.startPoint, event) + else: + self.complete(context) + if self.action == 'window': + self.feedback.instructions(context, "Select Polygons", "Click & Drag to select polygons in area", [ + ('SHIFT', 'deselect'), + ('CTRL', 'contains'), + ('A', 'All'), + ('I', 'Inverse'), + ('B', 'Bigger than current'), + ('F', 'Create a window from selection'), + ('ESC or RIGHTMOUSE', 'exit tool when done') + ]) + elif self.action == 'door': + self.feedback.instructions(context, "Select Polygons", "Click & Drag to select polygons in area", [ + ('SHIFT', 'deselect'), + ('CTRL', 'contains'), + ('A', 'All'), + ('I', 'Inverse'), + ('B', 'Bigger than current'), + ('F', 'Create a door from selection'), + ('ESC or RIGHTMOUSE', 'exit tool when done') + ]) + self.selectMode = True + if event.type == 'MOUSEMOVE': + self.endPoint = (event.mouse_region_x, event.mouse_region_y) + return {'RUNNING_MODAL'} + + def _draw_2d_arc(self, context, c, p0, p1): + """ + draw projection of 3d arc in 2d space + """ + d0 = np.subtract(c, p0) + d1 = np.subtract(p1, c) + a0 = atan2(d0[1], d0[0]) + a1 = atan2(d1[1], d1[0]) + da = a1 - a0 + if da < pi: + da += 2 * pi + if da > pi: + da -= 2 * pi + da = da / 12 + r = np.linalg.norm(d1) + pts = [] + for i in range(13): + a = a0 + da * i + p3d = c + Vector((cos(a) * r, sin(a) * r, 0)) + pts.append(self.coordsys.world * p3d) + + self.gl_arc.set_pos(pts) + self.gl_arc.draw(context) + self.gl_line.p = self.coordsys.world * c + self.gl_line.v = pts[0] - self.gl_line.p + self.gl_line.draw(context) + + def draw_callback(self, _self, context): + """ + draw on screen feedback using gl. + """ + self.feedback.draw(context) + + if self.selectMode: + self.cursor_area.set_location(context, self.startPoint, self.endPoint) + self.cursor_fence.set_location(context, self.endPoint) + self.cursor_area.draw(context) + self.cursor_fence.draw(context) + else: + if self.drag: + x0, y0 = self.startPoint + x1, y1 = self.endPoint + # draw 2d line marker + # self.gl.Line(x0, y0, x1, y1, self.gl.line_colour) + + # 2d line + self.gl_side.p = Vector(self.startPoint) + self.gl_side.v = Vector(self.endPoint) - Vector(self.startPoint) + self.gl_side.draw(context) + + tM, w, h, l_pts, w_pts = self.object_location + pt = self._position_3d_from_coord(context, self.endPoint) + pt = tM.inverted() * Vector(pt) + self.need_rotation = pt.y < 0 + if self.action == 'door': + # symbole porte + if pt.x > 0: + if pt.y > 0: + self.direction = 1 + i_s, i_c, i_e = 3, 2, 1 + else: + self.direction = 0 + i_s, i_c, i_e = 2, 3, 0 + else: + if pt.y > 0: + self.direction = 0 + i_s, i_c, i_e = 0, 1, 2 + else: + self.direction = 1 + i_s, i_c, i_e = 1, 0, 3 + self._draw_2d_arc(context, w_pts[i_c], w_pts[i_s], w_pts[i_e]) + elif self.action == 'window': + # symbole fenetre + if pt.y > 0: + i_s0, i_c0 = 0, 1 + i_s1, i_c1 = 3, 2 + else: + i_s0, i_c0 = 1, 0 + i_s1, i_c1 = 2, 3 + pc = w_pts[i_c0] + 0.5 * (w_pts[i_c1] - w_pts[i_c0]) + self._draw_2d_arc(context, w_pts[i_c0], w_pts[i_s0], pc) + self._draw_2d_arc(context, w_pts[i_c1], w_pts[i_s1], pc) + + +class ARCHIPACK_OP_PolyLib_Pick2DPoints(Operator): + bl_idname = "archipack.polylib_pick_2d_points" + bl_label = "Pick lines" + bl_description = "Pick lines" + bl_options = {'REGISTER', 'UNDO'} + pass_keys = ['NUMPAD_0', 'NUMPAD_1', 'NUMPAD_3', 'NUMPAD_4', + 'NUMPAD_5', 'NUMPAD_6', 'NUMPAD_7', 'NUMPAD_8', + 'NUMPAD_9', 'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'] + action = StringProperty(name="action", default="select") + + @classmethod + def poll(self, context): + global vars_dict + return vars_dict['select_points'] is not None + + def modal(self, context, event): + global vars_dict + context.area.tag_redraw() + if event.type in self.pass_keys: + return {'PASS_THROUGH'} + return vars_dict['select_points'].modal(context, event) + + def invoke(self, context, event): + global vars_dict + if vars_dict['select_points'] is None: + self.report({'WARNING'}, "Use detect before") + return {'CANCELLED'} + elif context.space_data.type == 'VIEW_3D': + vars_dict['select_points'].init(self, context, self.action) + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + else: + self.report({'WARNING'}, "Active space must be a View3d") + return {'CANCELLED'} + + +class ARCHIPACK_OP_PolyLib_Pick2DLines(Operator): + bl_idname = "archipack.polylib_pick_2d_lines" + bl_label = "Pick lines" + bl_description = "Pick lines" + bl_options = {'REGISTER', 'UNDO'} + pass_keys = ['NUMPAD_0', 'NUMPAD_1', 'NUMPAD_3', 'NUMPAD_4', + 'NUMPAD_5', 'NUMPAD_6', 'NUMPAD_7', 'NUMPAD_8', + 'NUMPAD_9', 'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'] + action = StringProperty(name="action", default="select") + + @classmethod + def poll(self, context): + global vars_dict + return vars_dict['select_lines'] is not None + + def modal(self, context, event): + global vars_dict + context.area.tag_redraw() + if event.type in self.pass_keys: + return {'PASS_THROUGH'} + return vars_dict['select_lines'].modal(context, event) + + def invoke(self, context, event): + global vars_dict + if vars_dict['select_lines'] is None: + self.report({'WARNING'}, "Use detect before") + return {'CANCELLED'} + elif context.space_data.type == 'VIEW_3D': + vars_dict['select_lines'].init(self, context, self.action) + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + else: + self.report({'WARNING'}, "Active space must be a View3d") + return {'CANCELLED'} + + +class ARCHIPACK_OP_PolyLib_Pick2DPolygons(Operator): + bl_idname = "archipack.polylib_pick_2d_polygons" + bl_label = "Pick 2d" + bl_description = "Pick polygons" + bl_options = {'REGISTER', 'UNDO'} + pass_keys = ['NUMPAD_0', 'NUMPAD_1', 'NUMPAD_3', 'NUMPAD_4', + 'NUMPAD_5', 'NUMPAD_6', 'NUMPAD_7', 'NUMPAD_8', + 'NUMPAD_9', 'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'] + action = StringProperty(name="action", default="select") + + @classmethod + def poll(self, context): + global vars_dict + return vars_dict['select_polygons'] is not None + + def modal(self, context, event): + global vars_dict + context.area.tag_redraw() + if event.type in self.pass_keys: + return {'PASS_THROUGH'} + return vars_dict['select_polygons'].modal(context, event) + + def invoke(self, context, event): + global vars_dict + if vars_dict['select_polygons'] is None: + self.report({'WARNING'}, "Use detect before") + return {'CANCELLED'} + elif context.space_data.type == 'VIEW_3D': + vars_dict['select_polygons'].init(self, context, self.action) + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + else: + self.report({'WARNING'}, "Active space must be a View3d") + return {'CANCELLED'} + + +class ARCHIPACK_OP_PolyLib_Detect(Operator): + bl_idname = "archipack.polylib_detect" + bl_label = "Detect Polygons" + bl_description = "Detect polygons from unordered splines" + bl_options = {'REGISTER', 'UNDO'} + extend = FloatProperty(name="extend", default=0.01, subtype='DISTANCE', unit='LENGTH', min=0) + + @classmethod + def poll(self, context): + return len(context.selected_objects) > 0 and context.object is not None and context.object.type == 'CURVE' + + def execute(self, context): + global vars_dict + print("Detect") + t = time.time() + objs = [obj for obj in context.selected_objects if obj.type == 'CURVE'] + + if len(objs) < 1: + self.report({'WARNING'}, "Select a curve object before") + return {'CANCELLED'} + + for obj in objs: + obj.select = False + + coordsys = CoordSys(objs) + + vars_dict['point_tree'] = Qtree(coordsys, extend=0.5 * EPSILON) + vars_dict['seg_tree'] = Qtree(coordsys, extend=self.extend) + + # Shape based union + shapes = [] + Io.curves_to_shapes(objs, coordsys, context.window_manager.archipack_polylib.resolution, shapes) + union = ShapeOps.union(shapes, self.extend) + + # output select points + vars_dict['select_points'] = SelectPoints(shapes, coordsys) + + geoms = Io.shapes_to_geoms(union) + + # output select_lines + vars_dict['select_lines'] = SelectLines(geoms, coordsys) + + # Shapely based union + # vars_dict['select_polygons'].io.curves_as_shapely(objs, lines) + # geoms = vars_dict['select_polygons'].ops.union(lines, self.extend) + + result, dangles, cuts, invalids = ShapelyOps.detect_polygons(geoms) + vars_dict['select_polygons'] = SelectPolygons(result, coordsys) + + if len(invalids) > 0: + errs = Io.to_curve(context.scene, coordsys, invalids, "invalid_polygons") + err_mat = vars_dict['select_polygons'].build_display_mat("Invalid_polygon", (1, 0, 0)) + # curve.data.bevel_depth = 0.02 + errs.color = (1, 0, 0, 1) + if len(errs.data.materials) < 1: + errs.data.materials.append(err_mat) + errs.active_material = err_mat + errs.select = True + self.report({'WARNING'}, str(len(invalids)) + " invalid polygons detected") + print("Detect :%.2f seconds polygons:%s invalids:%s" % (time.time() - t, len(result), len(invalids))) + return {'FINISHED'} + + +class ARCHIPACK_OP_PolyLib_Offset(Operator): + bl_idname = "archipack.polylib_offset" + bl_label = "Offset" + bl_description = "Offset lines" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return len(context.selected_objects) > 0 and context.object is not None and context.object.type == 'CURVE' + + def execute(self, context): + wm = context.window_manager.archipack_polylib + objs = list(obj for obj in context.selected_objects if obj.type == 'CURVE') + if len(objs) < 1: + self.report({'WARNING'}, "Select a curve object before") + return {'CANCELLED'} + for obj in objs: + obj.select = False + lines = [] + coordsys = Io.curves_to_geoms(objs, wm.resolution, lines) + offset = [] + for line in lines: + res = line.parallel_offset(wm.offset_distance, wm.offset_side, resolution=wm.offset_resolution, + join_style=int(wm.offset_join_style), mitre_limit=wm.offset_mitre_limit) + offset.append(res) + Io.to_curve(context.scene, coordsys, offset, 'offset') + return {'FINISHED'} + + +class ARCHIPACK_OP_PolyLib_Simplify(Operator): + bl_idname = "archipack.polylib_simplify" + bl_label = "Simplify" + bl_description = "Simplify lines" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return (len(context.selected_objects) > 0 and + context.object is not None and + context.object.type == 'CURVE') + + def execute(self, context): + global vars_dict + wm = context.window_manager.archipack_polylib + objs = [obj for obj in context.selected_objects if obj.type == 'CURVE'] + if len(objs) < 1: + self.report({'WARNING'}, "Select a curve object before") + return {'CANCELLED'} + for obj in objs: + obj.select = False + simple = [] + lines = [] + coordsys = Io.curves_to_geoms(objs, wm.resolution, lines) + for line in lines: + res = line.simplify(wm.simplify_tolerance, preserve_topology=wm.simplify_preserve_topology) + simple.append(res) + Io.to_curve(context.scene, coordsys, simple, 'simplify') + return {'FINISHED'} + + +class ARCHIPACK_OP_PolyLib_OutputPolygons(Operator): + bl_idname = "archipack.polylib_output_polygons" + bl_label = "Output Polygons" + bl_description = "Output all polygons" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + global vars_dict + return vars_dict['select_polygons'] is not None + + def execute(self, context): + global vars_dict + result = Io.to_curve(context.scene, vars_dict['select_polygons'].coordsys, + vars_dict['select_polygons'].geoms, 'polygons') + context.scene.objects.active = result + return {'FINISHED'} + + +class ARCHIPACK_OP_PolyLib_OutputLines(Operator): + bl_idname = "archipack.polylib_output_lines" + bl_label = "Output lines" + bl_description = "Output all lines" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + global vars_dict + return vars_dict['select_lines'] is not None + + def execute(self, context): + global vars_dict + result = Io.to_curve(context.scene, vars_dict['select_lines'].coordsys, + vars_dict['select_lines'].geoms, 'lines') + context.scene.objects.active = result + return {'FINISHED'} + + +class ARCHIPACK_OP_PolyLib_Solidify(Operator): + bl_idname = "archipack.polylib_solidify" + bl_label = "Extrude" + bl_description = "Extrude all polygons" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return (len(context.selected_objects) > 0 and + context.object is not None and + context.object.type == 'CURVE') + + def execute(self, context): + wm = context.window_manager.archipack_polylib + objs = [obj for obj in context.selected_objects if obj.type == 'CURVE'] + if len(objs) < 1: + self.report({'WARNING'}, "Select a curve object before") + return {'CANCELLED'} + for obj in objs: + obj.data.dimensions = '2D' + mod = obj.modifiers.new("Solidify", 'SOLIDIFY') + mod.thickness = wm.solidify_thickness + mod.offset = 1.00 + mod.use_even_offset = True + mod.use_quality_normals = True + return {'FINISHED'} + + +class archipack_polylib(PropertyGroup): + bl_idname = 'archipack.polylib_parameters' + extend = FloatProperty( + name="Extend", + description="Extend to closest intersecting segment", + default=0.01, + subtype='DISTANCE', unit='LENGTH', min=0 + ) + offset_distance = FloatProperty( + name="Distance", + default=0.05, + subtype='DISTANCE', unit='LENGTH', min=0 + ) + offset_side = EnumProperty( + name="Side", default='left', + items=[('left', 'Left', 'Left'), + ('right', 'Right', 'Right')] + ) + offset_resolution = IntProperty( + name="Resolution", default=16 + ) + offset_join_style = EnumProperty( + name="Style", default='2', + items=[('1', 'Round', 'Round'), + ('2', 'Mitre', 'Mitre'), + ('3', 'Bevel', 'Bevel')] + ) + offset_mitre_limit = FloatProperty( + name="Mitre limit", + default=10.0, + subtype='DISTANCE', + unit='LENGTH', min=0 + ) + simplify_tolerance = FloatProperty( + name="Tolerance", + default=0.01, + subtype='DISTANCE', unit='LENGTH', min=0 + ) + simplify_preserve_topology = BoolProperty( + name="Preserve topology", + description="Preserve topology (fast without, but may introduce self crossing)", + default=True + ) + solidify_thickness = FloatProperty( + name="Thickness", + default=2.7, + subtype='DISTANCE', unit='LENGTH', min=0 + ) + resolution = IntProperty( + name="Bezier resolution", min=0, default=12 + ) + + +@persistent +def load_handler(dummy): + global vars_dict + vars_dict['select_polygons'] = None + vars_dict['select_lines'] = None + vars_dict['seg_tree'] = None + vars_dict['point_tree'] = None + + +def register(): + global vars_dict + vars_dict = { + # spacial tree for segments and points + 'seg_tree': None, + 'point_tree': None, + # keep track of shapely geometry selection sets + 'select_polygons': None, + 'select_lines': None, + 'select_points': None + } + bpy.utils.register_class(ARCHIPACK_OP_PolyLib_Pick2DPolygons) + bpy.utils.register_class(ARCHIPACK_OP_PolyLib_Pick2DLines) + bpy.utils.register_class(ARCHIPACK_OP_PolyLib_Pick2DPoints) + bpy.utils.register_class(ARCHIPACK_OP_PolyLib_OutputPolygons) + bpy.utils.register_class(ARCHIPACK_OP_PolyLib_OutputLines) + bpy.utils.register_class(ARCHIPACK_OP_PolyLib_Offset) + bpy.utils.register_class(ARCHIPACK_OP_PolyLib_Simplify) + bpy.utils.register_class(ARCHIPACK_OP_PolyLib_Detect) + bpy.utils.register_class(ARCHIPACK_OP_PolyLib_Solidify) + bpy.utils.register_class(archipack_polylib) + bpy.types.WindowManager.archipack_polylib = PointerProperty(type=archipack_polylib) + bpy.app.handlers.load_post.append(load_handler) + + +def unregister(): + global vars_dict + del vars_dict + bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_Pick2DPolygons) + bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_Pick2DLines) + bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_Pick2DPoints) + bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_Detect) + bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_OutputPolygons) + bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_OutputLines) + bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_Offset) + bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_Simplify) + bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_Solidify) + bpy.utils.unregister_class(archipack_polylib) + bpy.app.handlers.load_post.remove(load_handler) + del bpy.types.WindowManager.archipack_polylib + + +if __name__ == "__main__": + register() diff --git a/archipack/archipack_preset.py b/archipack/archipack_preset.py new file mode 100644 index 000000000..c5fe94460 --- /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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# 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 diff --git a/archipack/archipack_reference_point.py b/archipack/archipack_reference_point.py new file mode 100644 index 000000000..d81a6839a --- /dev/null +++ b/archipack/archipack_reference_point.py @@ -0,0 +1,368 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +import bpy +from bpy.types import Operator, PropertyGroup, Object, Panel +from bpy.props import ( + FloatVectorProperty, + CollectionProperty, + FloatProperty + ) +from mathutils import Vector +from .bmesh_utils import BmeshEdit as bmed + + +def update(self, context): + self.update(context) + + +class archipack_reference_point(PropertyGroup): + location_2d = FloatVectorProperty( + subtype='XYZ', + name="position 2d", + default=Vector((0, 0, 0)) + ) + location_3d = FloatVectorProperty( + subtype='XYZ', + name="position 3d", + default=Vector((0, 0, 0)) + ) + symbol_scale = FloatProperty( + name="Screen scale", + default=1, + min=0.01, + update=update) + + @classmethod + def filter(cls, o): + """ + Filter object with this class in data + return + True when object contains this datablock + False otherwhise + usage: + class_name.filter(object) from outside world + self.__class__.filter(object) from instance + """ + try: + return cls.__name__ in o + except: + pass + return False + + @classmethod + def datablock(cls, o): + """ + Retrieve datablock from base object + return + datablock when found + None when not found + usage: + class_name.datablock(object) from outside world + self.__class__.datablock(object) from instance + """ + try: + return getattr(o, cls.__name__)[0] + except: + pass + return None + + def update(self, context): + + o = context.active_object + + if self.datablock(o) != self: + return + + s = self.symbol_scale + verts = [(s * x, s * y, s * z) for x, y, z in [ + (-0.25, 0.25, 0.0), (0.25, 0.25, 0.0), (-0.25, -0.25, 0.0), (0.25, -0.25, 0.0), + (0.0, 0.0, 0.487), (-0.107, 0.107, 0.216), (0.108, 0.107, 0.216), (-0.107, -0.107, 0.216), + (0.108, -0.107, 0.216), (-0.05, 0.05, 0.5), (0.05, 0.05, 0.5), (0.05, -0.05, 0.5), + (-0.05, -0.05, 0.5), (-0.193, 0.193, 0.0), (0.193, 0.193, 0.0), (0.193, -0.193, 0.0), + (-0.193, -0.193, 0.0), (0.0, 0.0, 0.8), (0.0, 0.8, -0.0), (0.0, 0.0, -0.0), + (0.0, 0.0, 0.0), (0.05, 0.05, 0.674), (-0.05, 0.674, -0.05), (0.0, 0.8, -0.0), + (-0.05, -0.05, 0.674), (-0.05, 0.674, 0.05), (0.05, 0.674, -0.05), (-0.129, 0.129, 0.162), + (0.129, 0.129, 0.162), (-0.129, -0.129, 0.162), (0.129, -0.129, 0.162), (0.0, 0.0, 0.8), + (-0.05, 0.05, 0.674), (0.05, -0.05, 0.674), (0.05, 0.674, 0.05), (0.8, -0.0, -0.0), + (0.0, -0.0, -0.0), (0.674, 0.05, -0.05), (0.8, -0.0, -0.0), (0.674, 0.05, 0.05), + (0.674, -0.05, -0.05), (0.674, -0.05, 0.05)]] + + edges = [(1, 0), (0, 9), (9, 10), (10, 1), (3, 1), (10, 11), + (11, 3), (2, 3), (11, 12), (12, 2), (0, 2), (12, 9), + (6, 5), (8, 6), (7, 8), (5, 7), (17, 24), (17, 20), + (18, 25), (18, 19), (13, 14), (14, 15), (15, 16), (16, 13), + (4, 6), (15, 30), (17, 21), (26, 22), (23, 22), (23, 34), + (18, 26), (28, 27), (30, 28), (29, 30), (27, 29), (14, 28), + (13, 27), (16, 29), (4, 7), (4, 8), (4, 5), (31, 33), + (31, 32), (21, 32), (24, 32), (24, 33), (21, 33), (25, 22), + (25, 34), (26, 34), (35, 39), (35, 36), (40, 37), (38, 37), + (38, 41), (35, 40), (39, 37), (39, 41), (40, 41)] + + bm = bmed._start(context, o) + bm.clear() + for v in verts: + bm.verts.new(v) + bm.verts.ensure_lookup_table() + for ed in edges: + bm.edges.new((bm.verts[ed[0]], bm.verts[ed[1]])) + bmed._end(bm, o) + + +class ARCHIPACK_PT_reference_point(Panel): + bl_idname = "ARCHIPACK_PT_reference_point" + bl_label = "Reference point" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'ArchiPack' + + @classmethod + def poll(cls, context): + return archipack_reference_point.filter(context.active_object) + + def draw(self, context): + o = context.active_object + props = archipack_reference_point.datablock(o) + if props is None: + return + layout = self.layout + if (o.location - props.location_2d).length < 0.01: + layout.operator('archipack.move_to_3d') + layout.operator('archipack.move_2d_reference_to_cursor') + else: + layout.operator('archipack.move_to_2d') + + layout.prop(props, 'symbol_scale') + + +class ARCHIPACK_OT_reference_point(Operator): + """Add reference point""" + bl_idname = "archipack.reference_point" + bl_label = "Reference point" + bl_description = "Add reference point" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + location_3d = FloatVectorProperty( + subtype='XYZ', + name="position 3d", + default=Vector((0, 0, 0)) + ) + + @classmethod + def poll(cls, context): + return context.active_object is not None + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def create(self, context): + x, y, z = context.scene.cursor_location + # bpy.ops.object.empty_add(type='ARROWS', radius=0.5, location=Vector((x, y, 0))) + m = bpy.data.meshes.new(name="Reference") + o = bpy.data.objects.new("Reference", m) + o.location = Vector((x, y, 0)) + context.scene.objects.link(o) + d = o.archipack_reference_point.add() + d.location_2d = Vector((x, y, 0)) + d.location_3d = self.location_3d + o.select = True + context.scene.objects.active = o + d.update(context) + return o + + def execute(self, context): + if context.mode == "OBJECT": + o = self.create(context) + o.select = True + context.scene.objects.active = o + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_move_to_3d(Operator): + bl_idname = "archipack.move_to_3d" + bl_label = "Move to 3d" + bl_description = "Move point to 3d position" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return archipack_reference_point.filter(context.active_object) + + def execute(self, context): + if context.mode == "OBJECT": + o = context.active_object + props = archipack_reference_point.datablock(o) + if props is None: + return {'CANCELLED'} + o.location = props.location_3d + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_move_to_2d(Operator): + bl_idname = "archipack.move_to_2d" + bl_label = "Move to 2d" + bl_description = "Move point to 2d position" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return archipack_reference_point.filter(context.active_object) + + def execute(self, context): + if context.mode == "OBJECT": + o = context.active_object + props = archipack_reference_point.datablock(o) + if props is None: + return {'CANCELLED'} + props.location_3d = o.location + o.location = props.location_2d + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_store_2d_reference(Operator): + bl_idname = "archipack.store_2d_reference" + bl_label = "Set 2d" + bl_description = "Set 2d reference position" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return archipack_reference_point.filter(context.active_object) + + def execute(self, context): + if context.mode == "OBJECT": + o = context.active_object + props = archipack_reference_point.datablock(o) + if props is None: + return {'CANCELLED'} + x, y, z = o.location + props.location_2d = Vector((x, y, 0)) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_move_2d_reference_to_cursor(Operator): + bl_idname = "archipack.move_2d_reference_to_cursor" + bl_label = "Change 2d" + bl_description = "Change 2d reference position to cursor location without moving childs" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return archipack_reference_point.filter(context.active_object) + + def execute(self, context): + if context.mode == "OBJECT": + o = context.active_object + props = archipack_reference_point.datablock(o) + if props is None: + return {'CANCELLED'} + bpy.ops.object.select_all(action="DESELECT") + bpy.ops.archipack.reference_point(location_3d=props.location_3d) + for child in o.children: + child.select = True + bpy.ops.archipack.parent_to_reference() + context.scene.objects.unlink(o) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_parent_to_reference(Operator): + bl_idname = "archipack.parent_to_reference" + bl_label = "Parent" + bl_description = "Make selected object childs of parent reference point" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return archipack_reference_point.filter(context.active_object) + + def execute(self, context): + if context.mode == "OBJECT": + o = context.active_object + props = archipack_reference_point.datablock(o) + if props is None: + return {'CANCELLED'} + sel = [obj for obj in context.selected_objects if obj != o and obj.parent != o] + itM = o.matrix_world.inverted() + # print("parent_to_reference parenting:%s objects" % (len(sel))) + for child in sel: + rs = child.matrix_world.to_3x3().to_4x4() + loc = itM * child.matrix_world.translation + child.parent = None + child.matrix_parent_inverse.identity() + child.location = Vector((0, 0, 0)) + child.parent = o + child.matrix_world = rs + child.location = loc + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +def register(): + bpy.utils.register_class(archipack_reference_point) + Object.archipack_reference_point = CollectionProperty(type=archipack_reference_point) + bpy.utils.register_class(ARCHIPACK_PT_reference_point) + bpy.utils.register_class(ARCHIPACK_OT_reference_point) + bpy.utils.register_class(ARCHIPACK_OT_move_to_3d) + bpy.utils.register_class(ARCHIPACK_OT_move_to_2d) + bpy.utils.register_class(ARCHIPACK_OT_store_2d_reference) + bpy.utils.register_class(ARCHIPACK_OT_move_2d_reference_to_cursor) + bpy.utils.register_class(ARCHIPACK_OT_parent_to_reference) + + +def unregister(): + bpy.utils.unregister_class(archipack_reference_point) + del Object.archipack_reference_point + bpy.utils.unregister_class(ARCHIPACK_PT_reference_point) + bpy.utils.unregister_class(ARCHIPACK_OT_reference_point) + bpy.utils.unregister_class(ARCHIPACK_OT_move_to_3d) + bpy.utils.unregister_class(ARCHIPACK_OT_move_to_2d) + bpy.utils.unregister_class(ARCHIPACK_OT_store_2d_reference) + bpy.utils.unregister_class(ARCHIPACK_OT_move_2d_reference_to_cursor) + bpy.utils.unregister_class(ARCHIPACK_OT_parent_to_reference) diff --git a/archipack/archipack_rendering.py b/archipack/archipack_rendering.py new file mode 100644 index 000000000..3d86d4d8b --- /dev/null +++ b/archipack/archipack_rendering.py @@ -0,0 +1,529 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# support routines for render measures in final image +# Author: Antonio Vazquez (antonioya) +# Archipack adaptation by : Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +# noinspection PyUnresolvedReferences +import bpy +# noinspection PyUnresolvedReferences +import bgl +from os import path, remove +from sys import exc_info +# noinspection PyUnresolvedReferences +import bpy_extras.image_utils as img_utils +# noinspection PyUnresolvedReferences +from math import ceil +from bpy.types import Operator + + +# ------------------------------------------------------------- +# Defines button for render +# +# ------------------------------------------------------------- +class ARCHIPACK_OT_render(Operator): + bl_idname = "archipack.render" + bl_label = "Render" + bl_category = 'Archipack' + bl_description = "Create a render image with measures. Use UV/Image editor to view image generated" + bl_category = 'Archipack' + + # -------------------------------------------------------------------- + # Get the final render image and return as image object + # + # return None if no render available + # -------------------------------------------------------------------- + + def get_render_image(self, outpath): + saved = False + # noinspection PyBroadException + try: + # noinspection PyBroadException + try: + result = bpy.data.images['Render Result'] + if result.has_data is False: + # this save produce to fill data image + result.save_render(outpath) + saved = True + except: + print("No render image found") + return None + + # Save and reload + if saved is False: + result.save_render(outpath) + + img = img_utils.load_image(outpath) + + return img + except: + print("Unexpected render image error") + return None + + # ------------------------------------- + # Save image to file + # ------------------------------------- + + def save_image(self, filepath, myimage): + # noinspection PyBroadException + try: + + # Save old info + settings = bpy.context.scene.render.image_settings + myformat = settings.file_format + mode = settings.color_mode + depth = settings.color_depth + + # Apply new info and save + settings.file_format = 'PNG' + settings.color_mode = "RGBA" + settings.color_depth = '8' + myimage.save_render(filepath) + print("Archipack: Image " + filepath + " saved") + + # Restore old info + settings.file_format = myformat + settings.color_mode = mode + settings.color_depth = depth + except: + print("Unexpected error:" + str(exc_info())) + self.report({'ERROR'}, "Archipack: Unable to save render image") + return + + # ------------------------------------------------------------- + # Render image main entry point + # + # ------------------------------------------------------------- + + def render_main(self, context, objlist, animation=False): + # noinspection PyBroadException,PyBroadException + # Save old info + scene = context.scene + render = scene.render + settings = render.image_settings + depth = settings.color_depth + settings.color_depth = '8' + # noinspection PyBroadException + try: + + # Get visible layers + layers = [] + for x in range(0, 20): + if scene.layers[x] is True: + layers.extend([x]) + + # -------------------- + # Get resolution + # -------------------- + render_scale = render.resolution_percentage / 100 + + width = int(render.resolution_x * render_scale) + height = int(render.resolution_y * render_scale) + # --------------------------------------- + # Get output path + # --------------------------------------- + temp_path = path.realpath(bpy.app.tempdir) + if len(temp_path) > 0: + outpath = path.join(temp_path, "archipack_tmp_render.png") + else: + self.report({'ERROR'}, + "Archipack: Unable to save temporary render image. Define a valid temp path") + settings.color_depth = depth + return False + + # Get Render Image + img = self.get_render_image(outpath) + if img is None: + self.report({'ERROR'}, + "Archipack: Unable to save temporary render image. Define a valid temp path") + settings.color_depth = depth + return False + + # ----------------------------- + # Calculate rows and columns + # ----------------------------- + tile_x = 240 + tile_y = 216 + row_num = ceil(height / tile_y) + col_num = ceil(width / tile_x) + print("Archipack: Image divided in " + str(row_num) + "x" + str(col_num) + " tiles") + + # pixels out of visible area + cut4 = (col_num * tile_x * 4) - width * 4 # pixels aout of drawing area + totpixel4 = width * height * 4 # total pixels RGBA + + viewport_info = bgl.Buffer(bgl.GL_INT, 4) + bgl.glGetIntegerv(bgl.GL_VIEWPORT, viewport_info) + + # Load image on memory + img.gl_load(0, bgl.GL_NEAREST, bgl.GL_NEAREST) + + # 2.77 API change + if bpy.app.version >= (2, 77, 0): + tex = img.bindcode[0] + else: + tex = img.bindcode + + # -------------------------------------------- + # Create output image (to apply texture) + # -------------------------------------------- + if "archipack_output" in bpy.data.images: + out_img = bpy.data.images["archipack_output"] + if out_img is not None: + out_img.user_clear() + bpy.data.images.remove(out_img) + + out = bpy.data.images.new("archipack_output", width, height) + tmp_pixels = [1] * totpixel4 + + # -------------------------------- + # Loop for all tiles + # -------------------------------- + for row in range(0, row_num): + for col in range(0, 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) + + bgl.glMatrixMode(bgl.GL_PROJECTION) + bgl.glLoadIdentity() + + # defines ortographic view for single tile + x1 = tile_x * col + y1 = tile_y * row + bgl.gluOrtho2D(x1, x1 + tile_x, y1, y1 + tile_y) + + # Clear + bgl.glClearColor(0.0, 0.0, 0.0, 0.0) + bgl.glClear(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_DEPTH_BUFFER_BIT) + + bgl.glEnable(bgl.GL_TEXTURE_2D) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, tex) + + # defines drawing area + bgl.glBegin(bgl.GL_QUADS) + + bgl.glColor3f(1.0, 1.0, 1.0) + bgl.glTexCoord2f(0.0, 0.0) + bgl.glVertex2f(0.0, 0.0) + + bgl.glTexCoord2f(1.0, 0.0) + bgl.glVertex2f(width, 0.0) + + bgl.glTexCoord2f(1.0, 1.0) + bgl.glVertex2f(width, height) + + bgl.glTexCoord2f(0.0, 1.0) + bgl.glVertex2f(0.0, height) + + bgl.glEnd() + + # ----------------------------- + # Loop to draw all lines + # ----------------------------- + for o, d in objlist: + if o.hide is False: + # verify visible layer + for x in range(0, 20): + if o.layers[x] is True: + if x in layers: + context.scene.objects.active = o + # print("%s: %s" % (o.name, d.manip_stack)) + manipulators = d.manip_stack + if manipulators is not None: + for m in manipulators: + if m is not None: + m.draw_callback(m, context, render=True) + break + + # ----------------------------- + # Loop to draw all debug + # ----------------------------- + """ + if scene.archipack_debug is True: + selobj = bpy.context.selected_objects + for myobj in selobj: + if scene.archipack_debug_vertices is True: + draw_vertices(context, myobj, None, None) + if scene.archipack_debug_faces is True or scene.archipack_debug_normals is True: + draw_faces(context, myobj, None, None) + """ + """ + if scene.archipack_rf is True: + bgl.glColor3f(1.0, 1.0, 1.0) + rfcolor = scene.archipack_rf_color + rfborder = scene.archipack_rf_border + rfline = scene.archipack_rf_line + + bgl.glLineWidth(rfline) + bgl.glColor4f(rfcolor[0], rfcolor[1], rfcolor[2], rfcolor[3]) + + x1 = rfborder + x2 = width - rfborder + y1 = int(ceil(rfborder / (width / height))) + y2 = height - y1 + draw_rectangle((x1, y1), (x2, y2)) + """ + # -------------------------------- + # copy pixels to temporary area + # -------------------------------- + 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): + # final image pixels position + p1 = (y * width * 4) + (row * tile_y * width * 4) + (col * tile_x * 4) + p2 = p1 + (tile_x * 4) + # buffer pixels position + b1 = y * width * 4 + b2 = b1 + (tile_x * 4) + + if p1 < totpixel4: # avoid pixel row out of area + if col == col_num - 1: # avoid pixel columns out of area + p2 -= cut4 + b2 -= cut4 + + tmp_pixels[p1:p2] = buffer[b1:b2] + + # ----------------------- + # Copy temporary to final + # ----------------------- + out.pixels = tmp_pixels[:] # Assign image data + img.gl_free() # free opengl image memory + + # delete image + img.user_clear() + bpy.data.images.remove(img) + # remove temp file + remove(outpath) + # reset + bgl.glEnable(bgl.GL_SCISSOR_TEST) + # ----------------------- + # restore opengl defaults + # ----------------------- + bgl.glLineWidth(1) + bgl.glDisable(bgl.GL_BLEND) + bgl.glColor4f(0.0, 0.0, 0.0, 1.0) + # Saves image + if out is not None: + # and (scene.archipack_render is True or animation is True): + ren_path = bpy.context.scene.render.filepath + filename = "ap_frame" + if len(ren_path) > 0: + if ren_path.endswith(path.sep): + initpath = path.realpath(ren_path) + path.sep + else: + (initpath, filename) = path.split(ren_path) + + ftxt = "%04d" % scene.frame_current + outpath = path.realpath(path.join(initpath, filename + ftxt + ".png")) + + self.save_image(outpath, out) + + settings.color_depth = depth + return True + + except: + settings.color_depth = depth + print("Unexpected error:" + str(exc_info())) + self.report( + {'ERROR'}, + "Archipack: Unable to create render image. Be sure the output render path is correct" + ) + return False + + def get_objlist(self, context): + """ + Get objects with gl manipulators + """ + objlist = [] + for o in context.scene.objects: + if o.data is not None: + d = None + if 'archipack_window' in o.data: + d = o.data.archipack_window[0] + elif 'archipack_door' in o.data: + d = o.data.archipack_door[0] + elif 'archipack_wall2' in o.data: + d = o.data.archipack_wall2[0] + elif 'archipack_stair' in o.data: + d = o.data.archipack_stair[0] + elif 'archipack_fence' in o.data: + d = o.data.archipack_fence[0] + if d is not None: + objlist.append((o, d)) + return objlist + + def draw_gl(self, context): + objlist = self.get_objlist(context) + for o, d in objlist: + context.scene.objects.active = o + d.manipulable_disable(context) + d.manipulable_invoke(context) + return objlist + + def hide_gl(self, context, objlist): + for o, d in objlist: + context.scene.objects.active = o + d.manipulable_disable(context) + + # ------------------------------ + # Execute button action + # ------------------------------ + # noinspection PyMethodMayBeStatic,PyUnusedLocal + def execute(self, context): + scene = context.scene + wm = context.window_manager + msg = "New image created with measures. Open it in UV/image editor" + camera_msg = "Unable to render. No camera found" + + # ----------------------------- + # Check camera + # ----------------------------- + if scene.camera is None: + self.report({'ERROR'}, camera_msg) + return {'FINISHED'} + + objlist = self.draw_gl(context) + + # ----------------------------- + # Use current rendered image + # ----------------------------- + if wm.archipack.render_type == "1": + # noinspection PyBroadException + try: + result = bpy.data.images['Render Result'] + if result.has_data is False: + bpy.ops.render.render() + except: + bpy.ops.render.render() + + print("Archipack: Using current render image on buffer") + if self.render_main(context, objlist) is True: + self.report({'INFO'}, msg) + + # ----------------------------- + # OpenGL image + # ----------------------------- + elif wm.archipack.render_type == "2": + self.set_camera_view() + self.set_only_render(True) + + print("Archipack: Rendering opengl image") + bpy.ops.render.opengl() + if self.render_main(context, objlist) is True: + self.report({'INFO'}, msg) + + self.set_only_render(False) + + # ----------------------------- + # OpenGL Animation + # ----------------------------- + elif wm.archipack.render_type == "3": + oldframe = scene.frame_current + self.set_camera_view() + self.set_only_render(True) + flag = False + # loop frames + for frm in range(scene.frame_start, scene.frame_end + 1): + scene.frame_set(frm) + print("Archipack: Rendering opengl frame %04d" % frm) + bpy.ops.render.opengl() + flag = self.render_main(context, objlist, True) + if flag is False: + break + + self.set_only_render(False) + scene.frame_current = oldframe + if flag is True: + self.report({'INFO'}, msg) + + # ----------------------------- + # Image + # ----------------------------- + elif wm.archipack.render_type == "4": + print("Archipack: Rendering image") + bpy.ops.render.render() + if self.render_main(context, objlist) is True: + self.report({'INFO'}, msg) + + # ----------------------------- + # Animation + # ----------------------------- + elif wm.archipack.render_type == "5": + oldframe = scene.frame_current + flag = False + # loop frames + for frm in range(scene.frame_start, scene.frame_end + 1): + scene.frame_set(frm) + print("Archipack: Rendering frame %04d" % frm) + bpy.ops.render.render() + flag = self.render_main(context, objlist, True) + if flag is False: + break + + scene.frame_current = oldframe + if flag is True: + self.report({'INFO'}, msg) + + self.hide_gl(context, objlist) + + return {'FINISHED'} + + # --------------------- + # Set cameraView + # --------------------- + # noinspection PyMethodMayBeStatic + def set_camera_view(self): + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + area.spaces[0].region_3d.view_perspective = 'CAMERA' + + # ------------------------------------- + # Set only render status + # ------------------------------------- + # noinspection PyMethodMayBeStatic + def set_only_render(self, status): + screen = bpy.context.screen + + v3d = False + s = None + # get spaceview_3d in current screen + for a in screen.areas: + if a.type == 'VIEW_3D': + for s in a.spaces: + if s.type == 'VIEW_3D': + v3d = s + break + + if v3d is not False: + s.show_only_render = status + + +def register(): + bpy.utils.register_class(ARCHIPACK_OT_render) + + +def unregister(): + bpy.utils.unregister_class(ARCHIPACK_OT_render) diff --git a/archipack/archipack_slab.py b/archipack/archipack_slab.py new file mode 100644 index 000000000..d29c16784 --- /dev/null +++ b/archipack/archipack_slab.py @@ -0,0 +1,1505 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +# noinspection PyUnresolvedReferences +import bpy +# noinspection PyUnresolvedReferences +from bpy.types import Operator, PropertyGroup, Mesh, Panel +from bpy.props import ( + FloatProperty, BoolProperty, IntProperty, + StringProperty, EnumProperty, + CollectionProperty + ) +import bmesh +from mathutils import Vector, Matrix +from mathutils.geometry import interpolate_bezier +from math import sin, cos, pi, atan2 +from .archipack_manipulator import Manipulable, archipack_manipulator +from .archipack_object import ArchipackCreateTool, ArchipackObject +from .archipack_2d import Line, Arc + + +class Slab(): + + def __init__(self): + # self.colour_inactive = (1, 1, 1, 1) + pass + + def set_offset(self, offset, last=None): + """ + Offset line and compute intersection point + between segments + """ + self.line = self.make_offset(offset, last) + + def straight_slab(self, a0, length): + s = self.straight(length).rotate(a0) + return StraightSlab(s.p, s.v) + + def curved_slab(self, a0, da, radius): + n = self.normal(1).rotate(a0).scale(radius) + if da < 0: + n.v = -n.v + a0 = n.angle + c = n.p - n.v + return CurvedSlab(c, radius, a0, da) + + +class StraightSlab(Slab, Line): + + def __init__(self, p, v): + Line.__init__(self, p, v) + Slab.__init__(self) + + +class CurvedSlab(Slab, Arc): + + def __init__(self, c, radius, a0, da): + Arc.__init__(self, c, radius, a0, da) + Slab.__init__(self) + + +class SlabGenerator(): + + def __init__(self, parts): + self.parts = parts + self.segs = [] + + def add_part(self, part): + + if len(self.segs) < 1: + s = None + else: + s = self.segs[-1] + # start a new slab + if s is None: + if part.type == 'S_SEG': + p = Vector((0, 0)) + v = part.length * Vector((cos(part.a0), sin(part.a0))) + s = StraightSlab(p, v) + elif part.type == 'C_SEG': + c = -part.radius * Vector((cos(part.a0), sin(part.a0))) + s = CurvedSlab(c, part.radius, part.a0, part.da) + else: + if part.type == 'S_SEG': + s = s.straight_slab(part.a0, part.length) + elif part.type == 'C_SEG': + s = s.curved_slab(part.a0, part.da, part.radius) + + self.segs.append(s) + self.last_type = part.type + + def set_offset(self): + last = None + for i, seg in enumerate(self.segs): + seg.set_offset(self.parts[i].offset, last) + last = seg.line + + """ + def close(self, closed): + # Make last segment implicit closing one + if closed: + return + """ + + def close(self, closed): + # Make last segment implicit closing one + if closed: + part = self.parts[-1] + w = self.segs[-1] + dp = self.segs[0].p0 - self.segs[-1].p0 + if "C_" in part.type: + dw = (w.p1 - w.p0) + w.r = part.radius / dw.length * dp.length + # angle pt - p0 - angle p0 p1 + da = atan2(dp.y, dp.x) - atan2(dw.y, dw.x) + a0 = w.a0 + da + if a0 > pi: + a0 -= 2 * pi + if a0 < -pi: + a0 += 2 * pi + w.a0 = a0 + else: + w.v = dp + + if len(self.segs) > 1: + w.line = w.make_offset(self.parts[-1].offset, self.segs[-2]) + + w = self.segs[-1] + p1 = self.segs[0].line.p1 + self.segs[0].line = self.segs[0].make_offset(self.parts[0].offset, w.line) + self.segs[0].line.p1 = p1 + + def locate_manipulators(self): + """ + setup manipulators + """ + for i, f in enumerate(self.segs): + + manipulators = self.parts[i].manipulators + p0 = f.p0.to_3d() + p1 = f.p1.to_3d() + # angle from last to current segment + if i > 0: + v0 = self.segs[i - 1].straight(-1, 1).v.to_3d() + v1 = f.straight(1, 0).v.to_3d() + manipulators[0].set_pts([p0, v0, v1]) + + if type(f).__name__ == "StraightSlab": + # segment length + manipulators[1].type_key = 'SIZE' + manipulators[1].prop1_name = "length" + manipulators[1].set_pts([p0, p1, (1, 0, 0)]) + else: + # segment radius + angle + v0 = (f.p0 - f.c).to_3d() + v1 = (f.p1 - f.c).to_3d() + manipulators[1].type_key = 'ARC_ANGLE_RADIUS' + manipulators[1].prop1_name = "da" + manipulators[1].prop2_name = "radius" + manipulators[1].set_pts([f.c.to_3d(), v0, v1]) + + # snap manipulator, dont change index ! + manipulators[2].set_pts([p0, p1, (1, 0, 0)]) + # dumb segment id + manipulators[3].set_pts([p0, p1, (1, 0, 0)]) + + def get_verts(self, verts): + for slab in self.segs: + if "Curved" in type(slab).__name__: + for i in range(16): + x, y = slab.line.lerp(i / 16) + verts.append((x, y, 0)) + else: + x, y = slab.line.p0 + verts.append((x, y, 0)) + """ + for i in range(33): + x, y = slab.line.lerp(i / 32) + verts.append((x, y, 0)) + """ + + def rotate(self, idx_from, a): + """ + apply rotation to all following segs + """ + self.segs[idx_from].rotate(a) + ca = cos(a) + sa = sin(a) + rM = Matrix([ + [ca, -sa], + [sa, ca] + ]) + # rotation center + p0 = self.segs[idx_from].p0 + for i in range(idx_from + 1, len(self.segs)): + seg = self.segs[i] + # rotate seg + seg.rotate(a) + # rotate delta from rotation center to segment start + dp = rM * (seg.p0 - p0) + seg.translate(dp) + + def translate(self, idx_from, dp): + """ + apply translation to all following segs + """ + self.segs[idx_from].p1 += dp + for i in range(idx_from + 1, len(self.segs)): + self.segs[i].translate(dp) + + def draw(self, context): + """ + draw generator using gl + """ + for seg in self.segs: + seg.draw(context, render=False) + + +def update(self, context): + self.update(context) + + +def update_manipulators(self, context): + self.update(context, manipulable_refresh=True) + + +def update_path(self, context): + self.update_path(context) + + +materials_enum = ( + ('0', 'Ceiling', '', 0), + ('1', 'White', '', 1), + ('2', 'Concrete', '', 2), + ('3', 'Wood', '', 3), + ('4', 'Metal', '', 4), + ('5', 'Glass', '', 5) + ) + + +class archipack_slab_material(PropertyGroup): + index = EnumProperty( + items=materials_enum, + default='4', + update=update + ) + + def find_in_selection(self, context): + """ + find witch selected object this instance belongs to + provide support for "copy to selected" + """ + selected = [o for o in context.selected_objects] + for o in selected: + props = archipack_slab.datablock(o) + if props: + for part in props.rail_mat: + if part == self: + return props + return None + + def update(self, context): + props = self.find_in_selection(context) + if props is not None: + props.update(context) + + +class archipack_slab_child(PropertyGroup): + """ + Store child fences to be able to sync + """ + child_name = StringProperty() + idx = IntProperty() + + def get_child(self, context): + d = None + child = context.scene.objects.get(self.child_name) + if child is not None and child.data is not None: + if 'archipack_fence' in child.data: + d = child.data.archipack_fence[0] + return child, d + + +def update_type(self, context): + + d = self.find_in_selection(context) + + if d is not None and d.auto_update: + + d.auto_update = False + # find part index + idx = 0 + for i, part in enumerate(d.parts): + if part == self: + idx = i + break + + part = d.parts[idx] + a0 = 0 + if idx > 0: + g = d.get_generator() + w0 = g.segs[idx - 1] + a0 = w0.straight(1).angle + if "C_" in self.type: + w = w0.straight_slab(part.a0, part.length) + else: + w = w0.curved_slab(part.a0, part.da, part.radius) + else: + g = SlabGenerator(None) + g.add_part(self) + w = g.segs[0] + + # w0 - w - w1 + dp = w.p1 - w.p0 + if "C_" in self.type: + part.radius = 0.5 * dp.length + part.da = pi + a0 = atan2(dp.y, dp.x) - pi / 2 - a0 + else: + part.length = dp.length + a0 = atan2(dp.y, dp.x) - a0 + + if a0 > pi: + a0 -= 2 * pi + if a0 < -pi: + a0 += 2 * pi + part.a0 = a0 + + if idx + 1 < d.n_parts: + # adjust rotation of next part + part1 = d.parts[idx + 1] + if "C_" in part.type: + a0 = part1.a0 - pi / 2 + else: + a0 = part1.a0 + w.straight(1).angle - atan2(dp.y, dp.x) + + if a0 > pi: + a0 -= 2 * pi + if a0 < -pi: + a0 += 2 * pi + part1.a0 = a0 + + d.auto_update = True + + +class ArchipackSegment(): + """ + A single manipulable polyline like segment + polyline like segment line or arc based + @TODO: share this base class with + stair, wall, fence, slab + """ + type = EnumProperty( + items=( + ('S_SEG', 'Straight', '', 0), + ('C_SEG', 'Curved', '', 1), + ), + default='S_SEG', + update=update_type + ) + length = FloatProperty( + name="length", + min=0.01, + default=2.0, + update=update + ) + radius = FloatProperty( + name="radius", + min=0.5, + default=0.7, + update=update + ) + da = FloatProperty( + name="angle", + min=-pi, + max=pi, + default=pi / 2, + subtype='ANGLE', unit='ROTATION', + update=update + ) + a0 = FloatProperty( + name="start angle", + min=-2 * pi, + max=2 * pi, + default=0, + subtype='ANGLE', unit='ROTATION', + update=update + ) + offset = FloatProperty( + name="Offset", + description="Add to current segment offset", + default=0, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + linked_idx = IntProperty(default=-1) + + # @TODO: + # flag to handle wall's x_offset + # when set add wall offset value to segment offset + # pay attention at allowing per wall segment offset + + manipulators = CollectionProperty(type=archipack_manipulator) + + def find_in_selection(self, context): + raise NotImplementedError + + def update(self, context, manipulable_refresh=False): + props = self.find_in_selection(context) + if props is not None: + props.update(context, manipulable_refresh) + + def draw_insert(self, context, layout, index): + """ + May implement draw for insert / remove segment operators + """ + pass + + def draw(self, context, layout, index): + box = layout.box() + row = box.row() + row.prop(self, "type", text=str(index + 1)) + self.draw_insert(context, box, index) + if self.type in ['C_SEG']: + row = box.row() + row.prop(self, "radius") + row = box.row() + row.prop(self, "da") + else: + row = box.row() + row.prop(self, "length") + row = box.row() + row.prop(self, "a0") + row = box.row() + row.prop(self, "offset") + # row.prop(self, "linked_idx") + + +class archipack_slab_part(ArchipackSegment, PropertyGroup): + + def draw_insert(self, context, layout, index): + row = layout.row(align=True) + row.operator("archipack.slab_insert", text="Split").index = index + row.operator("archipack.slab_balcony", text="Balcony").index = index + row.operator("archipack.slab_remove", text="Remove").index = index + + def find_in_selection(self, context): + """ + find witch selected object this instance belongs to + provide support for "copy to selected" + """ + selected = [o for o in context.selected_objects] + for o in selected: + props = archipack_slab.datablock(o) + if props: + for part in props.parts: + if part == self: + return props + return None + + +class archipack_slab(ArchipackObject, Manipulable, PropertyGroup): + # boundary + n_parts = IntProperty( + name="parts", + min=1, + default=1, update=update_manipulators + ) + parts = CollectionProperty(type=archipack_slab_part) + closed = BoolProperty( + default=False, + name="Close", + update=update_manipulators + ) + # UI layout related + parts_expand = BoolProperty( + options={'SKIP_SAVE'}, + default=False + ) + + x_offset = FloatProperty( + name="x offset", + min=-1000, max=1000, + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + z = FloatProperty( + name="z", + default=0.3, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + auto_synch = BoolProperty( + name="AutoSynch", + description="Keep wall in synch when editing", + default=True, + update=update_manipulators + ) + # @TODO: + # Global slab offset + # will only affect slab parts sharing a wall + + childs = CollectionProperty(type=archipack_slab_child) + # Flag to prevent mesh update while making bulk changes over variables + # use : + # .auto_update = False + # bulk changes + # .auto_update = True + auto_update = BoolProperty( + options={'SKIP_SAVE'}, + default=True, + update=update_manipulators + ) + + def get_generator(self): + g = SlabGenerator(self.parts) + for part in self.parts: + # type, radius, da, length + g.add_part(part) + + g.set_offset() + + g.close(self.closed) + g.locate_manipulators() + return g + + def insert_part(self, context, where): + self.manipulable_disable(context) + self.auto_update = False + # the part we do split + part_0 = self.parts[where] + part_0.length /= 2 + part_0.da /= 2 + self.parts.add() + part_1 = self.parts[len(self.parts) - 1] + part_1.type = part_0.type + part_1.length = part_0.length + part_1.offset = part_0.offset + part_1.da = part_0.da + part_1.a0 = 0 + # move after current one + self.parts.move(len(self.parts) - 1, where + 1) + self.n_parts += 1 + for c in self.childs: + if c.idx > where: + c.idx += 1 + self.setup_manipulators() + self.auto_update = True + + def insert_balcony(self, context, where): + self.manipulable_disable(context) + self.auto_update = False + + # the part we do split + part_0 = self.parts[where] + part_0.length /= 3 + part_0.da /= 3 + + # 1st part 90deg + self.parts.add() + part_1 = self.parts[len(self.parts) - 1] + part_1.type = "S_SEG" + part_1.length = 1.5 + part_1.da = part_0.da + part_1.a0 = -pi / 2 + # move after current one + self.parts.move(len(self.parts) - 1, where + 1) + + # 2nd part -90deg + self.parts.add() + part_1 = self.parts[len(self.parts) - 1] + part_1.type = part_0.type + part_1.length = part_0.length + part_1.radius = part_0.radius + 1.5 + part_1.da = part_0.da + part_1.a0 = pi / 2 + # move after current one + self.parts.move(len(self.parts) - 1, where + 2) + + # 3nd part -90deg + self.parts.add() + part_1 = self.parts[len(self.parts) - 1] + part_1.type = "S_SEG" + part_1.length = 1.5 + part_1.da = part_0.da + part_1.a0 = pi / 2 + # move after current one + self.parts.move(len(self.parts) - 1, where + 3) + + # 4nd part -90deg + self.parts.add() + part_1 = self.parts[len(self.parts) - 1] + part_1.type = part_0.type + part_1.length = part_0.length + part_1.radius = part_0.radius + part_1.offset = part_0.offset + part_1.da = part_0.da + part_1.a0 = -pi / 2 + # move after current one + self.parts.move(len(self.parts) - 1, where + 4) + + self.n_parts += 4 + self.setup_manipulators() + + for c in self.childs: + if c.idx > where: + c.idx += 4 + + self.auto_update = True + g = self.get_generator() + + o = context.active_object + bpy.ops.archipack.fence(auto_manipulate=False) + c = context.active_object + c.select = True + c.data.archipack_fence[0].n_parts = 3 + c.select = False + # link to o + c.location = Vector((0, 0, 0)) + c.parent = o + c.location = g.segs[where + 1].p0.to_3d() + self.add_child(c.name, where + 1) + # c.matrix_world.translation = g.segs[where].p1.to_3d() + o.select = True + context.scene.objects.active = o + self.relocate_childs(context, o, g) + + def add_part(self, context, length): + self.manipulable_disable(context) + self.auto_update = False + p = self.parts.add() + p.length = length + self.n_parts += 1 + self.setup_manipulators() + self.auto_update = True + return p + + def add_child(self, name, idx): + c = self.childs.add() + c.child_name = name + c.idx = idx + + def setup_childs(self, o, g): + """ + Store childs + call after a boolean oop + """ + # print("setup_childs") + self.childs.clear() + itM = o.matrix_world.inverted() + + dmax = 0.2 + for c in o.children: + if (c.data and 'archipack_fence' in c.data): + pt = (itM * c.matrix_world.translation).to_2d() + for idx, seg in enumerate(g.segs): + # may be optimized with a bound check + res, d, t = seg.point_sur_segment(pt) + # p1 + # |-- x + # p0 + dist = abs(t) * seg.length + if dist < dmax and abs(d) < dmax: + # print("%s %s %s %s" % (idx, dist, d, c.name)) + self.add_child(c.name, idx) + + # synch wall + # store index of segments with p0 match + if self.auto_synch: + + if o.parent is not None: + + for i, part in enumerate(self.parts): + part.linked_idx = -1 + + # find first child wall + d = None + for c in o.parent.children: + if c.data and "archipack_wall2" in c.data: + d = c.data.archipack_wall2[0] + break + + if d is not None: + og = d.get_generator() + j = 0 + for i, part in enumerate(self.parts): + ji = j + while ji < d.n_parts + 1: + if (g.segs[i].p0 - og.segs[ji].p0).length < 0.005: + j = ji + 1 + part.linked_idx = ji + # print("link: %s to %s" % (i, ji)) + break + ji += 1 + + def relocate_childs(self, context, o, g): + """ + Move and resize childs after edition + """ + # print("relocate_childs") + + # Wall child syncro + # must store - idx of shared segs + # -> store this in parts provide 1:1 map + # share type: full, start only, end only + # -> may compute on the fly with idx stored + # when full segment does match + # -update type, radius, length, a0, and da + # when start only does match + # -update type, radius, a0 + # when end only does match + # -compute length/radius + # @TODO: + # handle p0 and p1 changes right in Generator (archipack_2d) + # and retrieve params from there + if self.auto_synch: + if o.parent is not None: + wall = None + + for child in o.parent.children: + if child.data and "archipack_wall2" in child.data: + wall = child + break + + if wall is not None: + d = wall.data.archipack_wall2[0] + d.auto_update = False + w = d.get_generator() + + last_idx = -1 + + # update og from g + for i, part in enumerate(self.parts): + idx = part.linked_idx + seg = g.segs[i] + + if i + 1 < self.n_parts: + next_idx = self.parts[i + 1].linked_idx + elif d.closed: + next_idx = self.parts[0].linked_idx + else: + next_idx = -1 + + if idx > -1: + + # start and shared: update rotation + a = seg.angle - w.segs[idx].angle + if abs(a) > 0.00001: + w.rotate(idx, a) + + if last_idx > -1: + w.segs[last_idx].p1 = seg.p0 + + if next_idx > -1: + + if idx + 1 == next_idx: + # shared: should move last point + # and apply to next segments + # this is overriden for common segs + # but translate non common ones + dp = seg.p1 - w.segs[idx].p1 + w.translate(idx, dp) + + # shared: transfert type too + if "C_" in part.type: + d.parts[idx].type = 'C_WALL' + w.segs[idx] = CurvedSlab(seg.c, seg.r, seg.a0, seg.da) + else: + d.parts[idx].type = 'S_WALL' + w.segs[idx] = StraightSlab(seg.p.copy(), seg.v.copy()) + last_idx = -1 + + elif next_idx > -1: + # only last is shared + # note: on next run will be part of start + last_idx = next_idx - 1 + + # update d from og + for i, seg in enumerate(w.segs): + if i > 0: + d.parts[i].a0 = seg.delta_angle(w.segs[i - 1]) + else: + d.parts[i].a0 = seg.angle + if "C_" in d.parts[i].type: + d.parts[i].radius = seg.r + d.parts[i].da = seg.da + else: + d.parts[i].length = max(0.01, seg.length) + + wall.select = True + context.scene.objects.active = wall + + d.auto_update = True + wall.select = False + + o.select = True + context.scene.objects.active = o + + wall.matrix_world = o.matrix_world.copy() + + tM = o.matrix_world + for child in self.childs: + c, d = child.get_child(context) + if c is None: + continue + + a = g.segs[child.idx].angle + x, y = g.segs[child.idx].p0 + sa = sin(a) + ca = cos(a) + + if d is not None: + c.select = True + + # auto_update need object to be active to + # setup manipulators on the right object + context.scene.objects.active = c + + d.auto_update = False + for i, part in enumerate(d.parts): + if "C_" in self.parts[i + child.idx].type: + part.type = "C_FENCE" + else: + part.type = "S_FENCE" + part.a0 = self.parts[i + child.idx].a0 + part.da = self.parts[i + child.idx].da + part.length = self.parts[i + child.idx].length + part.radius = self.parts[i + child.idx].radius + d.parts[0].a0 = pi / 2 + d.auto_update = True + c.select = False + + context.scene.objects.active = o + # preTranslate + c.matrix_world = tM * Matrix([ + [sa, ca, 0, x], + [-ca, sa, 0, y], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]) + + def remove_part(self, context, where): + self.manipulable_disable(context) + self.auto_update = False + + # preserve shape + # using generator + if where > 0: + + g = self.get_generator() + w = g.segs[where - 1] + w.p1 = g.segs[where].p1 + + if where + 1 < self.n_parts: + self.parts[where + 1].a0 = g.segs[where + 1].delta_angle(w) + + part = self.parts[where - 1] + + if "C_" in part.type: + part.radius = w.r + else: + part.length = w.length + + if where > 1: + part.a0 = w.delta_angle(g.segs[where - 2]) + else: + part.a0 = w.straight(1, 0).angle + + for c in self.childs: + if c.idx >= where: + c.idx -= 1 + self.parts.remove(where) + self.n_parts -= 1 + # fix snap manipulators index + self.setup_manipulators() + self.auto_update = True + + def update_parts(self, o, update_childs=False): + # print("update_parts") + # remove rows + # NOTE: + # n_parts+1 + # as last one is end point of last segment or closing one + row_change = False + for i in range(len(self.parts), self.n_parts, -1): + row_change = True + self.parts.remove(i - 1) + + # add rows + for i in range(len(self.parts), self.n_parts): + row_change = True + self.parts.add() + + self.setup_manipulators() + + g = self.get_generator() + + if o is not None and (row_change or update_childs): + self.setup_childs(o, g) + + return g + + def setup_manipulators(self): + + if len(self.manipulators) < 1: + s = self.manipulators.add() + s.type_key = "SIZE" + s.prop1_name = "z" + s.normal = Vector((0, 1, 0)) + + for i in range(self.n_parts): + p = self.parts[i] + n_manips = len(p.manipulators) + if n_manips < 1: + s = p.manipulators.add() + s.type_key = "ANGLE" + s.prop1_name = "a0" + if n_manips < 2: + s = p.manipulators.add() + s.type_key = "SIZE" + s.prop1_name = "length" + if n_manips < 3: + s = p.manipulators.add() + s.type_key = 'WALL_SNAP' + s.prop1_name = str(i) + s.prop2_name = 'z' + if n_manips < 4: + s = p.manipulators.add() + s.type_key = 'DUMB_STRING' + s.prop1_name = str(i + 1) + p.manipulators[2].prop1_name = str(i) + p.manipulators[3].prop1_name = str(i + 1) + + self.parts[-1].manipulators[0].type_key = 'DUMB_ANGLE' + + def is_cw(self, pts): + p0 = pts[0] + d = 0 + for p in pts[1:]: + d += (p.x * p0.y - p.y * p0.x) + p0 = p + return d > 0 + + def interpolate_bezier(self, pts, wM, p0, p1, resolution): + # straight segment, worth testing here + # since this can lower points count by a resolution factor + # use normalized to handle non linear t + if resolution == 0: + pts.append(wM * p0.co.to_3d()) + else: + v = (p1.co - p0.co).normalized() + d1 = (p0.handle_right - p0.co).normalized() + d2 = (p1.co - p1.handle_left).normalized() + if d1 == v and d2 == v: + pts.append(wM * p0.co.to_3d()) + else: + seg = interpolate_bezier(wM * p0.co, + wM * p0.handle_right, + wM * p1.handle_left, + wM * p1.co, + resolution + 1) + for i in range(resolution): + pts.append(seg[i].to_3d()) + + def from_spline(self, wM, resolution, spline): + pts = [] + if spline.type == 'POLY': + pts = [wM * p.co.to_3d() for p in spline.points] + if spline.use_cyclic_u: + pts.append(pts[0]) + elif spline.type == 'BEZIER': + points = spline.bezier_points + for i in range(1, len(points)): + p0 = points[i - 1] + p1 = points[i] + self.interpolate_bezier(pts, wM, p0, p1, resolution) + if spline.use_cyclic_u: + p0 = points[-1] + p1 = points[0] + self.interpolate_bezier(pts, wM, p0, p1, resolution) + pts.append(pts[0]) + else: + pts.append(wM * points[-1].co) + + self.from_points(pts, spline.use_cyclic_u) + + def from_points(self, pts, closed): + + if self.is_cw(pts): + pts = list(reversed(pts)) + + self.auto_update = False + + self.n_parts = len(pts) - 1 + + self.update_parts(None) + + p0 = pts.pop(0) + a0 = 0 + for i, p1 in enumerate(pts): + dp = p1 - p0 + da = atan2(dp.y, dp.x) - a0 + if da > pi: + da -= 2 * pi + if da < -pi: + da += 2 * pi + if i >= len(self.parts): + break + p = self.parts[i] + p.length = dp.to_2d().length + p.dz = dp.z + p.a0 = da + a0 += da + p0 = p1 + + self.closed = closed + self.auto_update = True + + def make_surface(self, o, verts): + bm = bmesh.new() + for v in verts: + bm.verts.new(v) + bm.verts.ensure_lookup_table() + for i in range(1, len(verts)): + bm.edges.new((bm.verts[i - 1], bm.verts[i])) + bm.edges.new((bm.verts[-1], bm.verts[0])) + bm.edges.ensure_lookup_table() + bmesh.ops.contextual_create(bm, geom=bm.edges) + bm.to_mesh(o.data) + bm.free() + + def unwrap_uv(self, o): + bm = bmesh.new() + bm.from_mesh(o.data) + for face in bm.faces: + face.select = face.material_index > 0 + bm.to_mesh(o.data) + bpy.ops.uv.cube_project(scale_to_bounds=False, correct_aspect=True) + + for face in bm.faces: + face.select = face.material_index < 1 + bm.to_mesh(o.data) + bpy.ops.uv.smart_project(use_aspect=True, stretch_to_bounds=False) + bm.free() + + def update(self, context, manipulable_refresh=False, update_childs=False): + + o = self.find_in_selection(context, self.auto_update) + + if o is None: + return + + # clean up manipulators before any data model change + if manipulable_refresh: + self.manipulable_disable(context) + + g = self.update_parts(o, update_childs) + + verts = [] + + g.get_verts(verts) + if len(verts) > 2: + self.make_surface(o, verts) + + modif = o.modifiers.get('Slab') + if modif is None: + modif = o.modifiers.new('Slab', 'SOLIDIFY') + modif.use_quality_normals = True + modif.use_even_offset = True + modif.material_offset_rim = 2 + modif.material_offset = 1 + + modif.thickness = self.z + modif.offset = 1.0 + o.data.use_auto_smooth = True + bpy.ops.object.shade_smooth() + + # Height + self.manipulators[0].set_pts([ + (0, 0, 0), + (0, 0, -self.z), + (-1, 0, 0) + ], normal=g.segs[0].straight(-1, 0).v.to_3d()) + + self.relocate_childs(context, o, g) + + # enable manipulators rebuild + if manipulable_refresh: + self.manipulable_refresh = True + + # restore context + self.restore_context(context) + + def manipulable_setup(self, context): + """ + NOTE: + this one assume context.active_object is the instance this + data belongs to, failing to do so will result in wrong + manipulators set on active object + """ + self.manipulable_disable(context) + + o = context.active_object + + self.setup_manipulators() + + for i, part in enumerate(self.parts): + if i >= self.n_parts: + break + + if i > 0: + # start angle + self.manip_stack.append(part.manipulators[0].setup(context, o, part)) + + # length / radius + angle + self.manip_stack.append(part.manipulators[1].setup(context, o, part)) + + # snap point + self.manip_stack.append(part.manipulators[2].setup(context, o, self)) + # index + self.manip_stack.append(part.manipulators[3].setup(context, o, self)) + + for m in self.manipulators: + self.manip_stack.append(m.setup(context, o, self)) + + def manipulable_invoke(self, context): + """ + call this in operator invoke() + """ + # print("manipulable_invoke") + if self.manipulate_mode: + self.manipulable_disable(context) + return False + + o = context.active_object + g = self.get_generator() + # setup childs manipulators + self.setup_childs(o, g) + self.manipulable_setup(context) + self.manipulate_mode = True + + self._manipulable_invoke(context) + + return True + + +class ARCHIPACK_PT_slab(Panel): + """Archipack Slab""" + bl_idname = "ARCHIPACK_PT_slab" + bl_label = "Slab" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + # bl_context = 'object' + bl_category = 'ArchiPack' + + @classmethod + def poll(cls, context): + return archipack_slab.filter(context.active_object) + + def draw(self, context): + prop = archipack_slab.datablock(context.active_object) + if prop is None: + return + layout = self.layout + row = layout.row(align=True) + # self.set_context_3dview(context, row) + row.operator('archipack.slab_manipulate', icon='HAND') + box = layout.box() + box.prop(prop, 'z') + box = layout.box() + box.prop(prop, 'auto_synch') + box = layout.box() + row = box.row() + if prop.parts_expand: + row.prop(prop, 'parts_expand', icon="TRIA_DOWN", icon_only=True, text="Parts", emboss=False) + box.prop(prop, 'n_parts') + # box.prop(prop, 'closed') + for i, part in enumerate(prop.parts): + part.draw(context, layout, i) + else: + row.prop(prop, 'parts_expand', icon="TRIA_RIGHT", icon_only=True, text="Parts", emboss=False) + + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_slab_insert(Operator): + bl_idname = "archipack.slab_insert" + bl_label = "Insert" + bl_description = "Insert part" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + index = IntProperty(default=0) + + def execute(self, context): + if context.mode == "OBJECT": + d = archipack_slab.datablock(context.active_object) + if d is None: + return {'CANCELLED'} + d.insert_part(context, self.index) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_slab_balcony(Operator): + bl_idname = "archipack.slab_balcony" + bl_label = "Insert" + bl_description = "Insert part" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + index = IntProperty(default=0) + + def execute(self, context): + if context.mode == "OBJECT": + d = archipack_slab.datablock(context.active_object) + if d is None: + return {'CANCELLED'} + d.insert_balcony(context, self.index) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_slab_remove(Operator): + bl_idname = "archipack.slab_remove" + bl_label = "Remove" + bl_description = "Remove part" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + index = IntProperty(default=0) + + def execute(self, context): + if context.mode == "OBJECT": + d = archipack_slab.datablock(context.active_object) + if d is None: + return {'CANCELLED'} + d.remove_part(context, self.index) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_slab(ArchipackCreateTool, Operator): + bl_idname = "archipack.slab" + bl_label = "Slab" + bl_description = "Slab" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + def create(self, context): + m = bpy.data.meshes.new("Slab") + o = bpy.data.objects.new("Slab", m) + d = m.archipack_slab.add() + # make manipulators selectable + d.manipulable_selectable = True + context.scene.objects.link(o) + o.select = True + context.scene.objects.active = o + self.load_preset(d) + self.add_material(o) + return o + + # ----------------------------------------------------- + # Execute + # ----------------------------------------------------- + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.location = bpy.context.scene.cursor_location + o.select = True + context.scene.objects.active = o + self.manipulate() + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_slab_from_curve(Operator): + bl_idname = "archipack.slab_from_curve" + bl_label = "Slab curve" + bl_description = "Create a slab from a curve" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + auto_manipulate = BoolProperty(default=True) + + @classmethod + def poll(self, context): + return context.active_object is not None and context.active_object.type == 'CURVE' + # ----------------------------------------------------- + # Draw (create UI interface) + # ----------------------------------------------------- + # noinspection PyUnusedLocal + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def create(self, context): + curve = context.active_object + bpy.ops.archipack.slab(auto_manipulate=self.auto_manipulate) + o = context.scene.objects.active + d = archipack_slab.datablock(o) + spline = curve.data.splines[0] + d.from_spline(curve.matrix_world, 12, spline) + if spline.type == 'POLY': + pt = spline.points[0].co + elif spline.type == 'BEZIER': + pt = spline.bezier_points[0].co + else: + pt = Vector((0, 0, 0)) + # pretranslate + o.matrix_world = curve.matrix_world * Matrix([ + [1, 0, 0, pt.x], + [0, 1, 0, pt.y], + [0, 0, 1, pt.z], + [0, 0, 0, 1] + ]) + return o + + # ----------------------------------------------------- + # Execute + # ----------------------------------------------------- + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + self.create(context) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_slab_from_wall(Operator): + bl_idname = "archipack.slab_from_wall" + bl_label = "->Slab" + bl_description = "Create a slab from a wall" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + auto_manipulate = BoolProperty(default=True) + ceiling = BoolProperty(default=False) + + @classmethod + def poll(self, context): + o = context.active_object + return o is not None and o.data is not None and 'archipack_wall2' in o.data + + def create(self, context): + wall = context.active_object + wd = wall.data.archipack_wall2[0] + bpy.ops.archipack.slab(auto_manipulate=False) + o = context.scene.objects.active + d = archipack_slab.datablock(o) + d.auto_update = False + d.closed = True + d.parts.clear() + d.n_parts = wd.n_parts + 1 + for part in wd.parts: + p = d.parts.add() + if "S_" in part.type: + p.type = "S_SEG" + else: + p.type = "C_SEG" + p.length = part.length + p.radius = part.radius + p.da = part.da + p.a0 = part.a0 + d.auto_update = True + # pretranslate + if self.ceiling: + o.matrix_world = Matrix([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, wd.z + d.z], + [0, 0, 0, 1], + ]) * wall.matrix_world + else: + o.matrix_world = wall.matrix_world.copy() + bpy.ops.object.select_all(action='DESELECT') + # parenting childs to wall reference point + if wall.parent is None: + x, y, z = wall.bound_box[0] + context.scene.cursor_location = wall.matrix_world * Vector((x, y, z)) + # fix issue #9 + context.scene.objects.active = wall + bpy.ops.archipack.reference_point() + else: + wall.parent.select = True + context.scene.objects.active = wall.parent + wall.select = True + o.select = True + bpy.ops.archipack.parent_to_reference() + wall.parent.select = False + + return o + + # ----------------------------------------------------- + # Execute + # ----------------------------------------------------- + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.select = True + context.scene.objects.active = o + if self.auto_manipulate: + bpy.ops.archipack.slab_manipulate('INVOKE_DEFAULT') + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +# ------------------------------------------------------------------ +# Define operator class to manipulate object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_slab_manipulate(Operator): + bl_idname = "archipack.slab_manipulate" + bl_label = "Manipulate" + bl_description = "Manipulate" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_slab.filter(context.active_object) + + def invoke(self, context, event): + d = archipack_slab.datablock(context.active_object) + d.manipulable_invoke(context) + return {'FINISHED'} + + +def register(): + bpy.utils.register_class(archipack_slab_material) + bpy.utils.register_class(archipack_slab_child) + bpy.utils.register_class(archipack_slab_part) + bpy.utils.register_class(archipack_slab) + Mesh.archipack_slab = CollectionProperty(type=archipack_slab) + bpy.utils.register_class(ARCHIPACK_PT_slab) + bpy.utils.register_class(ARCHIPACK_OT_slab) + bpy.utils.register_class(ARCHIPACK_OT_slab_insert) + bpy.utils.register_class(ARCHIPACK_OT_slab_balcony) + bpy.utils.register_class(ARCHIPACK_OT_slab_remove) + # bpy.utils.register_class(ARCHIPACK_OT_slab_manipulate_ctx) + bpy.utils.register_class(ARCHIPACK_OT_slab_manipulate) + bpy.utils.register_class(ARCHIPACK_OT_slab_from_curve) + bpy.utils.register_class(ARCHIPACK_OT_slab_from_wall) + + +def unregister(): + bpy.utils.unregister_class(archipack_slab_material) + bpy.utils.unregister_class(archipack_slab_child) + bpy.utils.unregister_class(archipack_slab_part) + bpy.utils.unregister_class(archipack_slab) + del Mesh.archipack_slab + bpy.utils.unregister_class(ARCHIPACK_PT_slab) + bpy.utils.unregister_class(ARCHIPACK_OT_slab) + bpy.utils.unregister_class(ARCHIPACK_OT_slab_insert) + bpy.utils.unregister_class(ARCHIPACK_OT_slab_balcony) + bpy.utils.unregister_class(ARCHIPACK_OT_slab_remove) + # bpy.utils.unregister_class(ARCHIPACK_OT_slab_manipulate_ctx) + bpy.utils.unregister_class(ARCHIPACK_OT_slab_manipulate) + bpy.utils.unregister_class(ARCHIPACK_OT_slab_from_curve) + bpy.utils.unregister_class(ARCHIPACK_OT_slab_from_wall) diff --git a/archipack/archipack_snap.py b/archipack/archipack_snap.py new file mode 100644 index 000000000..936a07d82 --- /dev/null +++ b/archipack/archipack_snap.py @@ -0,0 +1,309 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# Inspired by Okavango's np_point_move +# ---------------------------------------------------------- +""" + Usage: + from .archipack_snap import snap_point + + snap_point(takeloc, draw_callback, action_callback, constraint_axis) + + arguments: + + takeloc Vector3d location of point to snap + + constraint_axis boolean tuple for each axis + eg: (True, True, False) to constrtaint to xy plane + + draw_callback(context, sp) + sp.takeloc + sp.placeloc + sp.delta + + action_callback(context, event, state, sp) + state in {'SUCCESS', 'CANCEL'} + sp.takeloc + sp.placeloc + sp.delta + + with 3d Vectors + - delta = placeloc - takeloc + - takeloc + - placeloc + + + NOTE: + may change grid size to 0.1 round feature (SHIFT) + see https://blenderartists.org/forum/showthread.php?205158-Blender-2-5-Snap-mode-increment + then use a SHIFT use grid snap + +""" + +import bpy +from bpy.types import Operator +from mathutils import Vector, Matrix + + +def dumb_callback(context, event, state, sp): + return + + +def dumb_draw(sp, context): + return + + +class SnapStore: + """ + Global store + """ + callback = None + draw = None + helper = None + takeloc = Vector((0, 0, 0)) + placeloc = Vector((0, 0, 0)) + constraint_axis = (True, True, False) + helper_matrix = Matrix() + transform_orientation = 'GLOBAL' + release_confirm = True + instances_running = 0 + + # context related + act = None + sel = [] + use_snap = False + snap_element = None + snap_target = None + pivot_point = None + trans_orientation = None + + +def snap_point(takeloc=None, + draw=dumb_draw, + callback=dumb_callback, + takemat=None, + constraint_axis=(True, True, False), + transform_orientation='GLOBAL', + mode='OBJECT', + release_confirm=True): + """ + Invoke op from outside world + in a convenient importable function + + transform_orientation in [‘GLOBAL’, ‘LOCAL’, ‘NORMAL’, ‘GIMBAL’, ‘VIEW’] + + draw(sp, context) a draw callback + callback(context, event, state, sp) action callback + + Use either : + takeloc Vector, unconstraint or system axis constraints + takemat Matrix, constaint to this matrix as 'LOCAL' coordsys + The snap source helper use it as world matrix + so it is possible to constraint to user defined coordsys. + """ + SnapStore.draw = draw + SnapStore.callback = callback + SnapStore.constraint_axis = constraint_axis + SnapStore.release_confirm = release_confirm + if takemat is not None: + SnapStore.helper_matrix = takemat + takeloc = takemat.translation + transform_orientation = 'LOCAL' + elif takeloc is not None: + SnapStore.helper_matrix = Matrix().Translation(takeloc) + else: + raise ValueError("ArchipackSnap: Either takeloc or takemat must be defined") + SnapStore.takeloc = takeloc + SnapStore.placeloc = takeloc + SnapStore.transform_orientation = transform_orientation + + # @NOTE: unused mode var to switch between OBJECT and EDIT mode + # for ArchipackSnapBase to be able to handle both modes + # must implements corresponding helper create and delete actions + SnapStore.mode = mode + res = bpy.ops.archipack.snap('INVOKE_DEFAULT') + # return helper so we are able to move it "live" + return SnapStore.helper + +class ArchipackSnapBase(): + """ + Helper class for snap Operators + store and restore context + create and destroy helper + install and remove a draw_callback working while snapping + + store and provide access to 3d Vectors + in draw_callback and action_callback + - delta = placeloc - takeloc + - takeloc + - placeloc + """ + def __init__(self): + self._draw_handler = None + + def init(self, context, event): + # Store context data + if SnapStore.instances_running < 1: + SnapStore.sel = [o for o in context.selected_objects] + SnapStore.act = context.active_object + bpy.ops.object.select_all(action="DESELECT") + SnapStore.use_snap = context.tool_settings.use_snap + SnapStore.snap_element = context.tool_settings.snap_element + SnapStore.snap_target = context.tool_settings.snap_target + SnapStore.pivot_point = context.space_data.pivot_point + SnapStore.trans_orientation = context.space_data.transform_orientation + self.create_helper(context) + SnapStore.instances_running += 1 + # print("ArchipackSnapBase init: %s" % (SnapStore.instances_running)) + self.set_transform_orientation(context) + args = (self, context) + self._draw_handler = bpy.types.SpaceView3D.draw_handler_add(SnapStore.draw, args, 'WINDOW', 'POST_PIXEL') + + def exit(self, context): + bpy.types.SpaceView3D.draw_handler_remove(self._draw_handler, 'WINDOW') + # trick to allow launch 2nd instance + # via callback, preserve context as it + SnapStore.instances_running -= 1 + # print("ArchipackSnapBase exit: %s" % (SnapStore.instances_running)) + if SnapStore.instances_running > 0: + return + + self.destroy_helper(context) + # Restore original context + context.tool_settings.use_snap = SnapStore.use_snap + context.tool_settings.snap_element = SnapStore.snap_element + context.tool_settings.snap_target = SnapStore.snap_target + context.space_data.pivot_point = SnapStore.pivot_point + context.space_data.transform_orientation = SnapStore.trans_orientation + for o in SnapStore.sel: + o.select = True + if SnapStore.act is not None: + context.scene.objects.active = SnapStore.act + + def set_transform_orientation(self, context): + """ + Allow local constraint orientation to be set + """ + context.space_data.transform_orientation = SnapStore.transform_orientation + + def create_helper(self, context): + """ + Create a helper with fake user + or find older one in bpy data and relink to scene + currently only support OBJECT mode + + Do target helper be linked to scene in order to work ? + + """ + + helper_idx = bpy.data.objects.find('Archipack_snap_helper') + if helper_idx > -1: + helper = bpy.data.objects[helper_idx] + if context.scene.objects.find('Archipack_snap_helper') < 0: + context.scene.objects.link(helper) + else: + bpy.ops.object.add(type='MESH') + helper = context.active_object + helper.name = 'Archipack_snap_helper' + helper.use_fake_user = True + helper.data.use_fake_user = True + # hide snap helper + # helper.hide = True + helper.matrix_world = SnapStore.helper_matrix + helper.select = True + context.scene.objects.active = helper + SnapStore.helper = helper + + def destroy_helper(self, context): + """ + Unlink helper + currently only support OBJECT mode + """ + if SnapStore.helper is not None: + context.scene.objects.unlink(SnapStore.helper) + SnapStore.helper = None + + @property + def delta(self): + return self.placeloc - self.takeloc + + @property + def takeloc(self): + return SnapStore.takeloc + + @property + def placeloc(self): + # take from helper when there so the delta + # is working even while modal is running + if SnapStore.helper is not None: + return SnapStore.helper.location + else: + return SnapStore.placeloc + + +class ARCHIPACK_OT_snap(ArchipackSnapBase, Operator): + bl_idname = 'archipack.snap' + bl_label = 'Archipack snap' + bl_options = {'UNDO'} + + def modal(self, context, event): + # print("Snap.modal event %s %s" % (event.type, event.value)) + context.area.tag_redraw() + # NOTE: this part only run after transform LEFTMOUSE RELEASE + # or with ESC and RIGHTMOUSE + if event.type not in {'ESC', 'RIGHTMOUSE', 'LEFTMOUSE', 'MOUSEMOVE'}: + # print("Snap.modal skip unknown event %s %s" % (event.type, event.value)) + # self.report({'WARNING'}, "ARCHIPACK_OT_snap unknown event") + return{'PASS_THROUGH'} + if event.type in {'ESC', 'RIGHTMOUSE'}: + SnapStore.callback(context, event, 'CANCEL', self) + else: + SnapStore.placeloc = SnapStore.helper.location + SnapStore.callback(context, event, 'SUCCESS', self) + self.exit(context) + # self.report({'INFO'}, "ARCHIPACK_OT_snap exit") + return{'FINISHED'} + + def invoke(self, context, event): + if context.area.type == 'VIEW_3D': + # print("Snap.invoke event %s %s" % (event.type, event.value)) + self.init(context, event) + context.window_manager.modal_handler_add(self) + # print("SnapStore.transform_orientation%s" % (SnapStore.transform_orientation)) + bpy.ops.transform.translate('INVOKE_DEFAULT', + constraint_axis=SnapStore.constraint_axis, + constraint_orientation=SnapStore.transform_orientation, + release_confirm=SnapStore.release_confirm) + return {'RUNNING_MODAL'} + else: + self.report({'WARNING'}, "View3D not found, cannot run operator") + return {'FINISHED'} + + +def register(): + bpy.utils.register_class(ARCHIPACK_OT_snap) + + +def unregister(): + bpy.utils.unregister_class(ARCHIPACK_OT_snap) diff --git a/archipack/archipack_stair.py b/archipack/archipack_stair.py new file mode 100644 index 000000000..c7e7f02c1 --- /dev/null +++ b/archipack/archipack_stair.py @@ -0,0 +1,2849 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +# noinspection PyUnresolvedReferences +import bpy +# noinspection PyUnresolvedReferences +from bpy.types import Operator, PropertyGroup, Mesh, Panel +from bpy.props import ( + FloatProperty, BoolProperty, IntProperty, CollectionProperty, + StringProperty, EnumProperty, FloatVectorProperty + ) +from .bmesh_utils import BmeshEdit as bmed +from .panel import Panel as Lofter +from mathutils import Vector, Matrix +from math import sin, cos, pi, floor, acos +from .archipack_manipulator import Manipulable, archipack_manipulator +from .archipack_2d import Line, Arc +from .archipack_preset import ArchipackPreset, PresetMenuOperator +from .archipack_object import ArchipackCreateTool, ArchipackObject + + +class Stair(): + def __init__(self, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z): + self.steps_type = steps_type + self.nose_type = nose_type + self.l_shape = None + self.r_shape = None + self.next_type = 'NONE' + self.last_type = 'NONE' + self.z_mode = z_mode + # depth of open step + self.nose_z = nose_z + # size under the step on bottom + self.bottom_z = bottom_z + self.left_offset = left_offset + self.right_offset = right_offset + self.last_height = 0 + + def set_matids(self, matids): + self.idmat_top, self.idmat_step_front, self.idmat_raise, \ + self.idmat_side, self.idmat_bottom, self.idmat_step_side = matids + + def set_height(self, step_height, z0): + self.step_height = step_height + self.z0 = z0 + + @property + def height(self): + return self.n_step * self.step_height + + @property + def top_offset(self): + return self.t_step / self.step_depth + + @property + def top(self): + return self.z0 + self.height + + @property + def left_length(self): + return self.get_length("LEFT") + + @property + def right_length(self): + return self.get_length("RIGHT") + + def step_size(self, step_depth): + t_step, n_step = self.steps(step_depth) + self.n_step = n_step + self.t_step = t_step + self.step_depth = step_depth + return n_step + + def p3d_left(self, verts, p2d, i, t, landing=False): + x, y = p2d + nose_z = min(self.step_height, self.nose_z) + zl = self.z0 + t * self.height + zs = self.z0 + i * self.step_height + if self.z_mode == 'LINEAR': + z0 = max(0, zl) + z1 = z0 - self.bottom_z + verts.extend([(x, y, z0), (x, y, z1)]) + else: + if "FULL" in self.steps_type: + z0 = 0 + else: + z0 = max(0, zl - nose_z - self.bottom_z) + z3 = zs + max(0, self.step_height - nose_z) + z4 = zs + self.step_height + if landing: + if "FULL" in self.steps_type: + z2 = 0 + z1 = 0 + else: + z2 = max(0, min(z3, z3 - self.bottom_z)) + z1 = z2 + else: + z1 = min(z3, max(z0, zl - nose_z)) + z2 = min(z3, max(z1, zl)) + verts.extend([(x, y, z0), + (x, y, z1), + (x, y, z2), + (x, y, z3), + (x, y, z4)]) + + def p3d_right(self, verts, p2d, i, t, landing=False): + x, y = p2d + nose_z = min(self.step_height, self.nose_z) + zl = self.z0 + t * self.height + zs = self.z0 + i * self.step_height + if self.z_mode == 'LINEAR': + z0 = max(0, zl) + z1 = z0 - self.bottom_z + verts.extend([(x, y, z1), (x, y, z0)]) + else: + if "FULL" in self.steps_type: + z0 = 0 + else: + z0 = max(0, zl - nose_z - self.bottom_z) + z3 = zs + max(0, self.step_height - nose_z) + z4 = zs + self.step_height + if landing: + if "FULL" in self.steps_type: + z2 = 0 + z1 = 0 + else: + z2 = max(0, min(z3, z3 - self.bottom_z)) + z1 = z2 + else: + z1 = min(z3, max(z0, zl - nose_z)) + z2 = min(z3, max(z1, zl)) + verts.extend([(x, y, z4), + (x, y, z3), + (x, y, z2), + (x, y, z1), + (x, y, z0)]) + + def p3d_cstep_left(self, verts, p2d, i, t): + x, y = p2d + nose_z = min(self.step_height, self.nose_z) + zs = self.z0 + i * self.step_height + z3 = zs + max(0, self.step_height - nose_z) + z1 = min(z3, zs - nose_z) + verts.append((x, y, z1)) + verts.append((x, y, z3)) + + def p3d_cstep_right(self, verts, p2d, i, t): + x, y = p2d + nose_z = min(self.step_height, self.nose_z) + zs = self.z0 + i * self.step_height + z3 = zs + max(0, self.step_height - nose_z) + z1 = min(z3, zs - nose_z) + verts.append((x, y, z3)) + verts.append((x, y, z1)) + + def straight_stair(self, length): + self.next_type = 'STAIR' + s = self.straight(length) + return StraightStair(s.p, s.v, self.left_offset, self.right_offset, self.steps_type, + self.nose_type, self.z_mode, self.nose_z, self.bottom_z) + + def straight_landing(self, length, last_type='STAIR'): + self.next_type = 'LANDING' + s = self.straight(length) + return StraightLanding(s.p, s.v, self.left_offset, self.right_offset, self.steps_type, + self.nose_type, self.z_mode, self.nose_z, self.bottom_z, last_type=last_type) + + def curved_stair(self, da, radius, left_shape, right_shape, double_limit=pi): + self.next_type = 'STAIR' + n = self.normal(1) + n.v = radius * n.v.normalized() + if da < 0: + n.v = -n.v + a0 = n.angle + c = n.p - n.v + return CurvedStair(c, radius, a0, da, self.left_offset, self.right_offset, + self.steps_type, self.nose_type, self.z_mode, self.nose_z, self.bottom_z, + left_shape, right_shape, double_limit=double_limit) + + def curved_landing(self, da, radius, left_shape, right_shape, double_limit=pi, last_type='STAIR'): + self.next_type = 'LANDING' + n = self.normal(1) + n.v = radius * n.v.normalized() + if da < 0: + n.v = -n.v + a0 = n.angle + c = n.p - n.v + return CurvedLanding(c, radius, a0, da, self.left_offset, self.right_offset, + self.steps_type, self.nose_type, self.z_mode, self.nose_z, self.bottom_z, + left_shape, right_shape, double_limit=double_limit, last_type=last_type) + + def get_z(self, t, mode): + if mode == 'LINEAR': + return self.z0 + t * self.height + else: + step = 1 + floor(t / self.t_step) + return self.z0 + step * self.step_height + + def make_profile(self, t, side, profile, verts, faces, matids, next=None, tnext=0): + z0 = self.get_z(t, 'LINEAR') + dz1 = 0 + t, part, dz0, shape = self.get_part(t, side) + if next is not None: + tnext, next, dz1, shape1 = next.get_part(tnext, side) + xy, s = part.proj_xy(t, next) + v_xy = s * xy.to_3d() + z, s = part.proj_z(t, dz0, next, dz1) + v_z = s * Vector((-xy.y * z.x, xy.x * z.x, z.y)) + x, y = part.lerp(t) + verts += [Vector((x, y, z0)) + v.x * v_xy + v.y * v_z for v in profile] + + def project_uv(self, rM, uvs, verts, indexes, up_axis='Z'): + if up_axis == 'Z': + uvs.append([(rM * Vector(verts[i])).to_2d() for i in indexes]) + elif up_axis == 'Y': + uvs.append([(x, z) for x, y, z in [(rM * Vector(verts[i])) for i in indexes]]) + else: + uvs.append([(y, z) for x, y, z in [(rM * Vector(verts[i])) for i in indexes]]) + + def get_proj_matrix(self, part, t, nose_y): + # a matrix to project verts + # into uv space for horizontal parts of this step + # so uv = (rM * vertex).to_2d() + tl = t - nose_y / self.get_length("LEFT") + tr = t - nose_y / self.get_length("RIGHT") + t2, part, dz, shape = self.get_part(tl, "LEFT") + p0 = part.lerp(t2) + t2, part, dz, shape = self.get_part(tr, "RIGHT") + p1 = part.lerp(t2) + v = (p1 - p0).normalized() + return Matrix([ + [-v.y, v.x, 0, p0.x], + [v.x, v.y, 0, p0.y], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]).inverted() + + def _make_nose(self, i, s, verts, faces, matids, uvs, nose_y): + + t = self.t_step * i + + # a matrix to project verts + # into uv space for horizontal parts of this step + # so uv = (rM * vertex).to_2d() + rM = self.get_proj_matrix(self, t, nose_y) + + if self.z_mode == 'LINEAR': + return rM + + f = len(verts) + + tl = t - nose_y / self.get_length("LEFT") + tr = t - nose_y / self.get_length("RIGHT") + + t2, part, dz, shape = self.get_part(tl, "LEFT") + p0 = part.lerp(t2) + self.p3d_left(verts, p0, s, t2) + + t2, part, dz, shape = self.get_part(tr, "RIGHT") + p1 = part.lerp(t2) + self.p3d_right(verts, p1, s, t2) + + start = 3 + end = 6 + offset = 10 + + # left, top, right + matids.extend([self.idmat_step_side, + self.idmat_top, + self.idmat_step_side]) + + faces += [(f + j, f + j + 1, f + j + offset + 1, f + j + offset) for j in range(start, end)] + + u = nose_y + v = (p1 - p0).length + w = verts[f + 2][2] - verts[f + 3][2] + s = int((end - start) / 2) + + uvs += [[(u, verts[f + j][2]), (u, verts[f + j + 1][2]), + (0, verts[f + j + 1][2]), (0, verts[f + j][2])] for j in range(start, start + s)] + + uvs.append([(0, 0), (0, v), (u, v), (u, 0)]) + + uvs += [[(u, verts[f + j][2]), (u, verts[f + j + 1][2]), + (0, verts[f + j + 1][2]), (0, verts[f + j][2])] for j in range(start + s + 1, end)] + + if 'STRAIGHT' in self.nose_type or 'OPEN' in self.steps_type: + # face bottom + matids.append(self.idmat_bottom) + faces.append((f + end, f + start, f + offset + start, f + offset + end)) + uvs.append([(u, v), (u, 0), (0, 0), (0, v)]) + + if self.steps_type != 'OPEN': + if 'STRAIGHT' in self.nose_type: + # front face bottom straight + matids.append(self.idmat_raise) + faces.append((f + 12, f + 17, f + 16, f + 13)) + uvs.append([(0, w), (v, w), (v, 0), (0, 0)]) + + elif 'OBLIQUE' in self.nose_type: + # front face bottom oblique + matids.append(self.idmat_raise) + faces.append((f + 12, f + 17, f + 6, f + 3)) + + uvs.append([(0, w), (v, w), (v, 0), (0, 0)]) + + matids.append(self.idmat_side) + faces.append((f + 3, f + 13, f + 12)) + uvs.append([(0, 0), (u, 0), (u, w)]) + + matids.append(self.idmat_side) + faces.append((f + 6, f + 17, f + 16)) + uvs.append([(0, 0), (u, w), (u, 0)]) + + # front face top + w = verts[f + 3][2] - verts[f + 4][2] + matids.append(self.idmat_step_front) + faces.append((f + 4, f + 3, f + 6, f + 5)) + uvs.append([(0, 0), (0, w), (v, w), (v, 0)]) + return rM + + def make_faces(self, f, rM, verts, faces, matids, uvs): + + if self.z_mode == 'LINEAR': + start = 0 + end = 3 + offset = 4 + matids.extend([self.idmat_side, + self.idmat_top, + self.idmat_side, + self.idmat_bottom]) + elif "OPEN" in self.steps_type: + # faces dessus-dessous-lateral marches fermees + start = 3 + end = 6 + offset = 10 + matids.extend([self.idmat_step_side, + self.idmat_top, + self.idmat_step_side, + self.idmat_bottom]) + else: + # faces dessus-dessous-lateral marches fermees + start = 0 + end = 9 + offset = 10 + matids.extend([self.idmat_side, + self.idmat_side, + self.idmat_side, + self.idmat_step_side, + self.idmat_top, + self.idmat_step_side, + self.idmat_side, + self.idmat_side, + self.idmat_side, + self.idmat_bottom]) + + u_l0 = 0 + u_l1 = self.t_step * self.left_length + u_r0 = 0 + u_r1 = self.t_step * self.right_length + + s = int((end - start) / 2) + uvs += [[(u_l0, verts[f + j][2]), (u_l0, verts[f + j + 1][2]), + (u_l1, verts[f + j + offset + 1][2]), (u_l1, verts[f + j + offset][2])] for j in range(start, start + s)] + + self.project_uv(rM, uvs, verts, [f + start + s, f + start + s + 1, + f + start + s + offset + 1, f + start + s + offset]) + + uvs += [[(u_r0, verts[f + j][2]), (u_r0, verts[f + j + 1][2]), + (u_r1, verts[f + j + offset + 1][2]), (u_r1, verts[f + j + offset][2])] for j in range(start + s + 1, end)] + + self.project_uv(rM, uvs, verts, [f + end, f + start, f + offset + start, f + offset + end]) + + faces += [(f + j, f + j + 1, f + j + offset + 1, f + j + offset) for j in range(start, end)] + faces.append((f + end, f + start, f + offset + start, f + offset + end)) + + +class StraightStair(Stair, Line): + def __init__(self, p, v, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z): + Stair.__init__(self, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z) + Line.__init__(self, p, v) + self.l_line = self.offset(-left_offset) + self.r_line = self.offset(right_offset) + + def make_step(self, i, verts, faces, matids, uvs, nose_y=0): + + rM = self._make_nose(i, i, verts, faces, matids, uvs, nose_y) + + t0 = self.t_step * i + + f = len(verts) + + p = self.l_line.lerp(t0) + self.p3d_left(verts, p, i, t0) + p = self.r_line.lerp(t0) + self.p3d_right(verts, p, i, t0) + + t1 = t0 + self.t_step + + p = self.l_line.lerp(t1) + self.p3d_left(verts, p, i, t1) + p = self.r_line.lerp(t1) + self.p3d_right(verts, p, i, t1) + + self.make_faces(f, rM, verts, faces, matids, uvs) + + if "OPEN" in self.steps_type: + faces.append((f + 13, f + 14, f + 15, f + 16)) + matids.append(self.idmat_step_front) + uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)]) + + def get_length(self, side): + return self.length + + def get_lerp_vect(self, posts, side, i, t_step, respect_edges, z_offset=0, t0_abs=None): + if t0_abs is not None: + t0 = t0_abs + else: + t0 = i * t_step + t, part, dz, shape = self.get_part(t0, side) + dz /= part.length + n = part.normal(t) + z0 = self.get_z(t0, 'STEP') + z1 = self.get_z(t0, 'LINEAR') + posts.append((n, dz, z0, z1 + t0 * z_offset)) + return [t0] + + def n_posts(self, post_spacing, side, respect_edges): + return self.steps(post_spacing) + + def get_part(self, t, side): + if side == 'LEFT': + part = self.l_line + else: + part = self.r_line + return t, part, self.height, 'LINE' + + +class CurvedStair(Stair, Arc): + def __init__(self, c, radius, a0, da, left_offset, right_offset, steps_type, nose_type, + z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=pi): + + Stair.__init__(self, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z) + Arc.__init__(self, c, radius, a0, da) + self.l_shape = left_shape + self.r_shape = right_shape + self.edges_multiples = round(abs(da), 6) > double_limit + # left arc, tangeant at start and end + self.l_arc, self.l_t0, self.l_t1, self.l_tc = self.set_offset(-left_offset, left_shape) + self.r_arc, self.r_t0, self.r_t1, self.r_tc = self.set_offset(right_offset, right_shape) + + def set_offset(self, offset, shape): + arc = self.offset(offset) + t0 = arc.tangeant(0, 1) + t1 = arc.tangeant(1, 1) + tc = arc.tangeant(0.5, 1) + if self.edges_multiples: + i, p, t = t0.intersect(tc) + tc.v *= 2 * t + tc.p = p + i, p, t2 = tc.intersect(t1) + else: + i, p, t = t0.intersect(t1) + t0.v *= t + t1.p = p + t1.v *= t + return arc, t0, t1, tc + + def get_length(self, side): + if side == 'RIGHT': + arc = self.r_arc + shape = self.r_shape + t0 = self.r_t0 + else: + arc = self.l_arc + shape = self.l_shape + t0 = self.l_t0 + if shape == 'CIRCLE': + return arc.length + else: + if self.edges_multiples: + # two edges + return t0.length * 4 + else: + return t0.length * 2 + + def _make_step(self, t_step, i, s, verts, landing=False): + + tb = t_step * i + + f = len(verts) + + t, part, dz, shape = self.get_part(tb, "LEFT") + p = part.lerp(t) + self.p3d_left(verts, p, s, tb, landing) + + t, part, dz, shape = self.get_part(tb, "RIGHT") + p = part.lerp(t) + self.p3d_right(verts, p, s, tb, landing) + return f + + def _make_edge(self, t_step, i, j, f, rM, verts, faces, matids, uvs): + tb = t_step * i + # make edges verts after regular ones + if self.l_shape != 'CIRCLE' or self.r_shape != 'CIRCLE': + if self.edges_multiples: + # edge 1 + if tb < 0.25 and tb + t_step > 0.25: + f0 = f + f = len(verts) + if self.l_shape == 'CIRCLE': + self.p3d_left(verts, self.l_arc.lerp(0.25), j, 0.25) + else: + self.p3d_left(verts, self.l_tc.p, j, 0.25) + if self.r_shape == 'CIRCLE': + self.p3d_right(verts, self.r_arc.lerp(0.25), j, 0.25) + else: + self.p3d_right(verts, self.r_tc.p, j, 0.25) + self.make_faces(f0, rM, verts, faces, matids, uvs) + # edge 2 + if tb < 0.75 and tb + t_step > 0.75: + f0 = f + f = len(verts) + if self.l_shape == 'CIRCLE': + self.p3d_left(verts, self.l_arc.lerp(0.75), j, 0.75) + else: + self.p3d_left(verts, self.l_t1.p, j, 0.75) + if self.r_shape == 'CIRCLE': + self.p3d_right(verts, self.r_arc.lerp(0.75), j, 0.75) + else: + self.p3d_right(verts, self.r_t1.p, j, 0.75) + self.make_faces(f0, rM, verts, faces, matids, uvs) + else: + if tb < 0.5 and tb + t_step > 0.5: + f0 = f + f = len(verts) + # the step goes through the edge + if self.l_shape == 'CIRCLE': + self.p3d_left(verts, self.l_arc.lerp(0.5), j, 0.5) + else: + self.p3d_left(verts, self.l_t1.p, j, 0.5) + if self.r_shape == 'CIRCLE': + self.p3d_right(verts, self.r_arc.lerp(0.5), j, 0.5) + else: + self.p3d_right(verts, self.r_t1.p, j, 0.5) + self.make_faces(f0, rM, verts, faces, matids, uvs) + return f + + def make_step(self, i, verts, faces, matids, uvs, nose_y=0): + + # open stair with closed face + + # step nose + rM = self._make_nose(i, i, verts, faces, matids, uvs, nose_y) + f = 0 + if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE': + # every 6 degree + n_subs = max(1, int(abs(self.da) / pi * 30 / self.n_step)) + t_step = self.t_step / n_subs + for j in range(n_subs): + f0 = f + f = self._make_step(t_step, n_subs * i + j, i, verts) + if j > 0: + self.make_faces(f0, rM, verts, faces, matids, uvs) + f = self._make_edge(t_step, n_subs * i + j, i, f, rM, verts, faces, matids, uvs) + else: + f = self._make_step(self.t_step, i, i, verts) + f = self._make_edge(self.t_step, i, i, f, rM, verts, faces, matids, uvs) + + self._make_step(self.t_step, i + 1, i, verts) + self.make_faces(f, rM, verts, faces, matids, uvs) + + if "OPEN" in self.steps_type and self.z_mode != 'LINEAR': + # back face top + faces.append((f + 13, f + 14, f + 15, f + 16)) + matids.append(self.idmat_step_front) + uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)]) + + def get_part(self, t, side): + if side == 'RIGHT': + arc = self.r_arc + shape = self.r_shape + t0, t1, tc = self.r_t0, self.r_t1, self.r_tc + else: + arc = self.l_arc + shape = self.l_shape + t0, t1, tc = self.l_t0, self.l_t1, self.l_tc + if shape == 'CIRCLE': + return t, arc, self.height, shape + else: + if self.edges_multiples: + # two edges + if t <= 0.25: + return 4 * t, t0, 0.25 * self.height, shape + elif t <= 0.75: + return 2 * (t - 0.25), tc, 0.5 * self.height, shape + else: + return 4 * (t - 0.75), t1, 0.25 * self.height, shape + else: + if t <= 0.5: + return 2 * t, t0, 0.5 * self.height, shape + else: + return 2 * (t - 0.5), t1, 0.5 * self.height, shape + + def get_lerp_vect(self, posts, side, i, t_step, respect_edges, z_offset=0, t0_abs=None): + if t0_abs is not None: + t0 = t0_abs + else: + t0 = i * t_step + res = [t0] + t1 = t0 + t_step + zs = self.get_z(t0, 'STEP') + zl = self.get_z(t0, 'LINEAR') + + # vect normal + t, part, dz, shape = self.get_part(t0, side) + n = part.normal(t) + dz /= part.length + posts.append((n, dz, zs, zl + t0 * z_offset)) + + if shape != 'CIRCLE' and respect_edges: + if self.edges_multiples: + if t0 < 0.25 and t1 > 0.25: + zs = self.get_z(0.25, 'STEP') + zl = self.get_z(0.25, 'LINEAR') + t, part, dz, shape = self.get_part(0.25, side) + n = part.normal(1) + posts.append((n, dz, zs, zl + 0.25 * z_offset)) + res.append(0.25) + if t0 < 0.75 and t1 > 0.75: + zs = self.get_z(0.75, 'STEP') + zl = self.get_z(0.75, 'LINEAR') + t, part, dz, shape = self.get_part(0.75, side) + n = part.normal(1) + posts.append((n, dz, zs, zl + 0.75 * z_offset)) + res.append(0.75) + elif t0 < 0.5 and t1 > 0.5: + zs = self.get_z(0.5, 'STEP') + zl = self.get_z(0.5, 'LINEAR') + t, part, dz, shape = self.get_part(0.5, side) + n = part.normal(1) + posts.append((n, dz, zs, zl + 0.5 * z_offset)) + res.append(0.5) + return res + + def n_posts(self, post_spacing, side, respect_edges): + if side == 'LEFT': + arc, t0, shape = self.l_arc, self.l_t0, self.l_shape + else: + arc, t0, shape = self.r_arc, self.r_t0, self.r_shape + step_factor = 1 + if shape == 'CIRCLE': + length = arc.length + else: + if self.edges_multiples: + if respect_edges: + step_factor = 2 + length = 4 * t0.length + else: + length = 2 * t0.length + steps = step_factor * max(1, round(length / post_spacing, 0)) + # print("respect_edges:%s t_step:%s n_step:%s" % (respect_edges, 1.0 / steps, int(steps))) + return 1.0 / steps, int(steps) + + +class StraightLanding(StraightStair): + def __init__(self, p, v, left_offset, right_offset, steps_type, + nose_type, z_mode, nose_z, bottom_z, last_type='STAIR'): + + StraightStair.__init__(self, p, v, left_offset, right_offset, steps_type, + nose_type, z_mode, nose_z, bottom_z) + + self.last_type = last_type + + @property + def height(self): + return 0 + + @property + def top_offset(self): + return self.t_step / self.v.length + + @property + def top(self): + if self.next_type == 'LANDING': + return self.z0 + else: + return self.z0 + self.step_height + + def step_size(self, step_depth): + self.n_step = 1 + self.t_step = 1 + self.step_depth = step_depth + if self.last_type == 'LANDING': + return 0 + else: + return 1 + + def make_step(self, i, verts, faces, matids, uvs, nose_y=0): + + if i == 0 and self.last_type != 'LANDING': + rM = self._make_nose(i, 0, verts, faces, matids, uvs, nose_y) + else: + rM = self.get_proj_matrix(self.l_line, self.t_step * i, nose_y) + + f = len(verts) + j = 0 + t0 = self.t_step * i + + p = self.l_line.lerp(t0) + self.p3d_left(verts, p, j, t0) + + p = self.r_line.lerp(t0) + self.p3d_right(verts, p, j, t0) + + t1 = t0 + self.t_step + p = self.l_line.lerp(t1) + self.p3d_left(verts, p, j, t1, self.next_type != 'LANDING') + + p = self.r_line.lerp(t1) + self.p3d_right(verts, p, j, t1, self.next_type != 'LANDING') + + self.make_faces(f, rM, verts, faces, matids, uvs) + + if "OPEN" in self.steps_type and self.next_type != 'LANDING': + faces.append((f + 13, f + 14, f + 15, f + 16)) + matids.append(self.idmat_step_front) + uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)]) + + def straight_landing(self, length): + return Stair.straight_landing(self, length, last_type='LANDING') + + def curved_landing(self, da, radius, left_shape, right_shape, double_limit=pi): + return Stair.curved_landing(self, da, radius, left_shape, + right_shape, double_limit=double_limit, last_type='LANDING') + + def get_z(self, t, mode): + if mode == 'STEP': + return self.z0 + self.step_height + else: + return self.z0 + + +class CurvedLanding(CurvedStair): + def __init__(self, c, radius, a0, da, left_offset, right_offset, steps_type, + nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=pi, last_type='STAIR'): + + CurvedStair.__init__(self, c, radius, a0, da, left_offset, right_offset, steps_type, + nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=double_limit) + + self.last_type = last_type + + @property + def top_offset(self): + if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE': + return self.t_step / self.step_depth + else: + if self.edges_multiples: + return 0.5 / self.length + else: + return 1 / self.length + + @property + def height(self): + return 0 + + @property + def top(self): + if self.next_type == 'LANDING': + return self.z0 + else: + return self.z0 + self.step_height + + def step_size(self, step_depth): + if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE': + t_step, n_step = self.steps(step_depth) + else: + if self.edges_multiples: + t_step, n_step = 0.5, 2 + else: + t_step, n_step = 1, 1 + self.n_step = n_step + self.t_step = t_step + self.step_depth = step_depth + if self.last_type == 'LANDING': + return 0 + else: + return 1 + + def make_step(self, i, verts, faces, matids, uvs, nose_y=0): + + if i == 0 and 'LANDING' not in self.last_type: + rM = self._make_nose(i, 0, verts, faces, matids, uvs, nose_y) + else: + rM = self.get_proj_matrix(self.l_arc, self.t_step * i, nose_y) + + f = len(verts) + + if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE': + n_subs = max(1, int(abs(self.da / pi * 30 / self.n_step))) + t_step = self.t_step / n_subs + for j in range(n_subs): + f0 = f + f = self._make_step(t_step, n_subs * i + j, 0, verts) + if j > 0: + self.make_faces(f0, rM, verts, faces, matids, uvs) + f = self._make_edge(t_step, n_subs * i + j, 0, f, rM, verts, faces, matids, uvs) + else: + f = self._make_step(self.t_step, i, 0, verts) + f = self._make_edge(self.t_step, i, 0, f, rM, verts, faces, matids, uvs) + + self._make_step(self.t_step, i + 1, 0, verts, i == self.n_step - 1 and 'LANDING' not in self.next_type) + self.make_faces(f, rM, verts, faces, matids, uvs) + + if "OPEN" in self.steps_type and 'LANDING' not in self.next_type: + faces.append((f + 13, f + 14, f + 15, f + 16)) + matids.append(self.idmat_step_front) + uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)]) + + def straight_landing(self, length): + return Stair.straight_landing(self, length, last_type='LANDING') + + def curved_landing(self, da, radius, left_shape, right_shape, double_limit=pi): + return Stair.curved_landing(self, da, radius, left_shape, + right_shape, double_limit=double_limit, last_type='LANDING') + + def get_z(self, t, mode): + if mode == 'STEP': + return self.z0 + self.step_height + else: + return self.z0 + + +class StairGenerator(): + def __init__(self, parts): + self.parts = parts + self.last_type = 'NONE' + self.stairs = [] + self.steps_type = 'NONE' + self.sum_da = 0 + self.user_defined_post = None + self.user_defined_uvs = None + self.user_defined_mat = None + + def add_part(self, type, steps_type, nose_type, z_mode, nose_z, bottom_z, center, + radius, da, width_left, width_right, length, left_shape, right_shape): + + self.steps_type = steps_type + if len(self.stairs) < 1: + s = None + else: + s = self.stairs[-1] + + if "S_" not in type: + self.sum_da += da + + # start a new stair + if s is None: + if type == 'S_STAIR': + p = Vector((0, 0)) + v = Vector((0, length)) + s = StraightStair(p, v, width_left, width_right, steps_type, nose_type, z_mode, nose_z, bottom_z) + elif type == 'C_STAIR': + if da < 0: + c = Vector((radius, 0)) + else: + c = Vector((-radius, 0)) + s = CurvedStair(c, radius, 0, da, width_left, width_right, steps_type, + nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape) + elif type == 'D_STAIR': + if da < 0: + c = Vector((radius, 0)) + else: + c = Vector((-radius, 0)) + s = CurvedStair(c, radius, 0, da, width_left, width_right, steps_type, + nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=0) + elif type == 'S_LANDING': + p = Vector((0, 0)) + v = Vector((0, length)) + s = StraightLanding(p, v, width_left, width_right, steps_type, nose_type, z_mode, nose_z, bottom_z) + elif type == 'C_LANDING': + if da < 0: + c = Vector((radius, 0)) + else: + c = Vector((-radius, 0)) + s = CurvedLanding(c, radius, 0, da, width_left, width_right, steps_type, + nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape) + elif type == 'D_LANDING': + if da < 0: + c = Vector((radius, 0)) + else: + c = Vector((-radius, 0)) + s = CurvedLanding(c, radius, 0, da, width_left, width_right, steps_type, + nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=0) + else: + if type == 'S_STAIR': + s = s.straight_stair(length) + elif type == 'C_STAIR': + s = s.curved_stair(da, radius, left_shape, right_shape) + elif type == 'D_STAIR': + s = s.curved_stair(da, radius, left_shape, right_shape, double_limit=0) + elif type == 'S_LANDING': + s = s.straight_landing(length) + elif type == 'C_LANDING': + s = s.curved_landing(da, radius, left_shape, right_shape) + elif type == 'D_LANDING': + s = s.curved_landing(da, radius, left_shape, right_shape, double_limit=0) + self.stairs.append(s) + self.last_type = type + + def n_steps(self, step_depth): + n_steps = 0 + for stair in self.stairs: + n_steps += stair.step_size(step_depth) + return n_steps + + def set_height(self, step_height): + z = 0 + for stair in self.stairs: + stair.set_height(step_height, z) + z = stair.top + + def make_stair(self, height, step_depth, verts, faces, matids, uvs, nose_y=0): + n_steps = self.n_steps(step_depth) + self.set_height(height / n_steps) + + for s, stair in enumerate(self.stairs): + if s < len(self.parts): + manipulator = self.parts[s].manipulators[0] + # Store Gl Points for manipulators + if 'Curved' in type(stair).__name__: + c = stair.c + p0 = (stair.p0 - c).to_3d() + p1 = (stair.p1 - c).to_3d() + manipulator.set_pts([(c.x, c.y, stair.top), p0, p1]) + manipulator.type_key = 'ARC_ANGLE_RADIUS' + manipulator.prop1_name = 'da' + manipulator.prop2_name = 'radius' + else: + if self.sum_da > 0: + side = 1 + else: + side = -1 + v0 = stair.p0 + v1 = stair.p1 + manipulator.set_pts([(v0.x, v0.y, stair.top), (v1.x, v1.y, stair.top), (side, 0, 0)]) + manipulator.type_key = 'SIZE' + manipulator.prop1_name = 'length' + + for i in range(stair.n_step): + stair.make_step(i, verts, faces, matids, uvs, nose_y=nose_y) + if s < len(self.stairs) - 1 and self.steps_type != 'OPEN' and \ + 'Landing' in type(stair).__name__ and stair.next_type != "LANDING": + f = len(verts) - 10 + faces.append((f, f + 1, f + 8, f + 9)) + matids.append(self.stairs[-1].idmat_bottom) + u = verts[f + 1][2] - verts[f][2] + v = (Vector(verts[f]) - Vector(verts[f + 9])).length + uvs.append([(0, 0), (0, u), (v, u), (v, 0)]) + + if self.steps_type != 'OPEN' and len(self.stairs) > 0: + f = len(verts) - 10 + faces.append((f, f + 1, f + 2, f + 3, f + 4, f + 5, f + 6, f + 7, f + 8, f + 9)) + matids.append(self.stairs[-1].idmat_bottom) + uvs.append([(0, 0), (.1, 0), (.2, 0), (.3, 0), (.4, 0), (.4, 1), (.3, 1), (.2, 1), (.1, 1), (0, 1)]) + + def setup_user_defined_post(self, o, post_x, post_y, post_z): + self.user_defined_post = o + x = o.bound_box[6][0] - o.bound_box[0][0] + y = o.bound_box[6][1] - o.bound_box[0][1] + z = o.bound_box[6][2] - o.bound_box[0][2] + self.user_defined_post_scale = Vector((post_x / x, post_y / -y, post_z / z)) + m = o.data + # create vertex group lookup dictionary for names + vgroup_names = {vgroup.index: vgroup.name for vgroup in o.vertex_groups} + # create dictionary of vertex group assignments per vertex + self.vertex_groups = [[vgroup_names[g.group] for g in v.groups] for v in m.vertices] + # uvs + uv_act = m.uv_layers.active + if uv_act is not None: + uv_layer = uv_act.data + self.user_defined_uvs = [[uv_layer[li].uv for li in p.loop_indices] for p in m.polygons] + else: + self.user_defined_uvs = [[(0, 0) for i in p.vertices] for p in m.polygons] + # material ids + self.user_defined_mat = [p.material_index for p in m.polygons] + + def get_user_defined_post(self, tM, z0, z1, z2, slope, post_z, verts, faces, matids, uvs): + f = len(verts) + m = self.user_defined_post.data + for i, g in enumerate(self.vertex_groups): + co = m.vertices[i].co.copy() + co.x *= self.user_defined_post_scale.x + co.y *= self.user_defined_post_scale.y + co.z *= self.user_defined_post_scale.z + if 'Top' in g: + co.z += z2 + elif 'Bottom' in g: + co.z += 0 + else: + co.z += z1 + if 'Slope' in g: + co.z += co.y * slope + verts.append(tM * co) + matids += self.user_defined_mat + faces += [tuple([i + f for i in p.vertices]) for p in m.polygons] + uvs += self.user_defined_uvs + + def get_post(self, post, post_x, post_y, post_z, post_alt, sub_offset_x, + id_mat, verts, faces, matids, uvs, bottom="STEP"): + + n, dz, zs, zl = post + slope = dz * post_y + + if self.user_defined_post is not None: + if bottom == "STEP": + z0 = zs + else: + z0 = zl + z1 = zl - z0 + z2 = zl - z0 + x, y = -n.v.normalized() + tM = Matrix([ + [x, y, 0, n.p.x], + [y, -x, 0, n.p.y], + [0, 0, 1, z0 + post_alt], + [0, 0, 0, 1] + ]) + self.get_user_defined_post(tM, z0, z1, z2, dz, post_z, verts, faces, matids, uvs) + return + + z3 = zl + post_z + post_alt - slope + z4 = zl + post_z + post_alt + slope + if bottom == "STEP": + z0 = zs + post_alt + z1 = zs + post_alt + else: + z0 = zl + post_alt - slope + z1 = zl + post_alt + slope + vn = n.v.normalized() + dx = post_x * vn + dy = post_y * Vector((vn.y, -vn.x)) + oy = sub_offset_x * vn + x0, y0 = n.p - dx + dy + oy + x1, y1 = n.p - dx - dy + oy + x2, y2 = n.p + dx - dy + oy + x3, y3 = n.p + dx + dy + oy + f = len(verts) + verts.extend([(x0, y0, z0), (x0, y0, z3), + (x1, y1, z1), (x1, y1, z4), + (x2, y2, z1), (x2, y2, z4), + (x3, y3, z0), (x3, y3, z3)]) + faces.extend([(f, f + 1, f + 3, f + 2), + (f + 2, f + 3, f + 5, f + 4), + (f + 4, f + 5, f + 7, f + 6), + (f + 6, f + 7, f + 1, f), + (f, f + 2, f + 4, f + 6), + (f + 7, f + 5, f + 3, f + 1)]) + matids.extend([id_mat, id_mat, id_mat, id_mat, id_mat, id_mat]) + x = [(0, 0), (0, post_z), (post_x, post_z), (post_x, 0)] + y = [(0, 0), (0, post_z), (post_y, post_z), (post_y, 0)] + z = [(0, 0), (post_x, 0), (post_x, post_y), (0, post_y)] + uvs.extend([x, y, x, y, z, z]) + + def get_panel(self, subs, altitude, panel_x, panel_z, sub_offset_x, idmat, verts, faces, matids, uvs): + n_subs = len(subs) + if n_subs < 1: + return + f = len(verts) + x0 = sub_offset_x - 0.5 * panel_x + x1 = sub_offset_x + 0.5 * panel_x + z0 = 0 + z1 = panel_z + profile = [Vector((x0, z0)), Vector((x1, z0)), Vector((x1, z1)), Vector((x0, z1))] + user_path_uv_v = [] + n_sections = n_subs - 1 + n, dz, zs, zl = subs[0] + p0 = n.p + v0 = n.v.normalized() + for s, section in enumerate(subs): + n, dz, zs, zl = section + p1 = n.p + if s < n_sections: + v1 = subs[s + 1][0].v.normalized() + dir = (v0 + v1).normalized() + scale = 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1)))) + for p in profile: + x, y = n.p + scale * p.x * dir + z = zl + p.y + altitude + verts.append((x, y, z)) + if s > 0: + user_path_uv_v.append((p1 - p0).length) + p0 = p1 + v0 = v1 + + # build faces using Panel + lofter = Lofter( + # closed_shape, index, x, y, idmat + True, + [i for i in range(len(profile))], + [p.x for p in profile], + [p.y for p in profile], + [idmat for i in range(len(profile))], + closed_path=False, + user_path_uv_v=user_path_uv_v, + user_path_verts=n_subs + ) + faces += lofter.faces(16, offset=f, path_type='USER_DEFINED') + matids += lofter.mat(16, idmat, idmat, path_type='USER_DEFINED') + v = Vector((0, 0)) + uvs += lofter.uv(16, v, v, v, v, 0, v, 0, 0, path_type='USER_DEFINED') + + def reset_shapes(self): + for s, stair in enumerate(self.stairs): + if 'Curved' in type(stair).__name__: + stair.l_shape = self.parts[s].left_shape + stair.r_shape = self.parts[s].right_shape + + def make_subs(self, height, step_depth, x, y, z, post_y, altitude, bottom, side, slice, + post_spacing, sub_spacing, respect_edges, move_x, x_offset, sub_offset_x, mat, + verts, faces, matids, uvs): + + n_steps = self.n_steps(step_depth) + self.set_height(height / n_steps) + n_stairs = len(self.stairs) - 1 + subs = [] + + if side == "LEFT": + offset = move_x - x_offset + # offset_sub = offset - sub_offset_x + else: + offset = move_x + x_offset + # offset_sub = offset + sub_offset_x + + for s, stair in enumerate(self.stairs): + if 'Curved' in type(stair).__name__: + if side == "LEFT": + part = stair.l_arc + shape = stair.l_shape + else: + part = stair.r_arc + shape = stair.r_shape + # Note: use left part as reference for post distances + # use right part as reference for panels + stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(offset, shape) + stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(offset, shape) + else: + stair.l_line = stair.offset(offset) + stair.r_line = stair.offset(offset) + part = stair.l_line + + lerp_z = 0 + edge_t = 1 + edge_size = 0 + # interpolate z near end landing + if 'Landing' in type(stair).__name__ and stair.next_type == 'STAIR': + if not slice: + line = stair.normal(1).offset(self.stairs[s + 1].step_depth) + res, p, t_part = part.intersect(line) + # does perpendicular line intersects circle ? + if res: + edge_size = self.stairs[s + 1].step_depth / stair.get_length(side) + edge_t = 1 - edge_size + else: + # in this case, lerp z over one step + lerp_z = stair.step_height + + t_step, n_step = stair.n_posts(post_spacing, side, respect_edges) + + # space between posts + sp = stair.get_length(side) + # post size + t_post = post_y / sp + + if s == n_stairs: + n_step += 1 + for i in range(n_step): + res_t = stair.get_lerp_vect([], side, i, t_step, respect_edges) + # subs + if s < n_stairs or i < n_step - 1: + res_t.append((i + 1) * t_step) + for j in range(len(res_t) - 1): + t0 = res_t[j] + t_post + t1 = res_t[j + 1] - t_post + dt = t1 - t0 + n_subs = int(sp * dt / sub_spacing) + if n_subs > 0: + t_subs = dt / n_subs + for k in range(1, n_subs): + t = t0 + k * t_subs + stair.get_lerp_vect(subs, side, 1, t0 + k * t_subs, False) + if t > edge_t: + n, dz, z0, z1 = subs[-1] + subs[-1] = n, dz, z0, z1 + (t - edge_t) / edge_size * stair.step_height + if lerp_z > 0: + n, dz, z0, z1 = subs[-1] + subs[-1] = n, dz, z0, z1 + t * stair.step_height + + for i, post in enumerate(subs): + self.get_post(post, x, y, z, altitude, sub_offset_x, mat, verts, faces, matids, uvs, bottom=bottom) + + def make_post(self, height, step_depth, x, y, z, altitude, side, post_spacing, respect_edges, move_x, x_offset, mat, + verts, faces, matids, uvs): + n_steps = self.n_steps(step_depth) + self.set_height(height / n_steps) + l_posts = [] + n_stairs = len(self.stairs) - 1 + + for s, stair in enumerate(self.stairs): + if type(stair).__name__ in ['CurvedStair', 'CurvedLanding']: + stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(move_x - x_offset, stair.l_shape) + stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(move_x + x_offset, stair.r_shape) + else: + stair.l_line = stair.offset(move_x - x_offset) + stair.r_line = stair.offset(move_x + x_offset) + + t_step, n_step = stair.n_posts(post_spacing, side, respect_edges) + + if s == n_stairs: + n_step += 1 + for i in range(n_step): + stair.get_lerp_vect(l_posts, side, i, t_step, respect_edges) + + if s == n_stairs and i == n_step - 1: + n, dz, z0, z1 = l_posts[-1] + l_posts[-1] = (n, dz, z0 - stair.step_height, z1) + + for i, post in enumerate(l_posts): + self.get_post(post, x, y, z, altitude, 0, mat, verts, faces, matids, uvs) + + def make_panels(self, height, step_depth, x, z, post_y, altitude, side, post_spacing, + panel_dist, respect_edges, move_x, x_offset, sub_offset_x, mat, verts, faces, matids, uvs): + + n_steps = self.n_steps(step_depth) + self.set_height(height / n_steps) + subs = [] + n_stairs = len(self.stairs) - 1 + + if side == "LEFT": + offset = move_x - x_offset + else: + offset = move_x + x_offset + + for s, stair in enumerate(self.stairs): + + is_circle = False + if 'Curved' in type(stair).__name__: + if side == "LEFT": + is_circle = stair.l_shape == "CIRCLE" + shape = stair.l_shape + else: + is_circle = stair.r_shape == "CIRCLE" + shape = stair.r_shape + stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(offset, shape) + stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(offset, shape) + else: + stair.l_line = stair.offset(offset) + stair.r_line = stair.offset(offset) + + # space between posts + sp = stair.get_length(side) + + t_step, n_step = stair.n_posts(post_spacing, side, respect_edges) + + if is_circle and 'Curved' in type(stair).__name__: + panel_da = abs(stair.da) / pi * 180 / n_step + panel_step = max(1, int(panel_da / 6)) + else: + panel_step = 1 + + # post size + t_post = (post_y + panel_dist) / sp + + if s == n_stairs: + n_step += 1 + for i in range(n_step): + res_t = stair.get_lerp_vect([], side, i, t_step, respect_edges) + # subs + if s < n_stairs or i < n_step - 1: + res_t.append((i + 1) * t_step) + for j in range(len(res_t) - 1): + t0 = res_t[j] + t_post + t1 = res_t[j + 1] - t_post + dt = t1 - t0 + t_curve = dt / panel_step + if dt > 0: + panel = [] + for k in range(panel_step): + stair.get_lerp_vect(panel, side, 1, t_curve, True, t0_abs=t0 + k * t_curve) + stair.get_lerp_vect(panel, side, 1, t1, False) + subs.append(panel) + for sub in subs: + self.get_panel(sub, altitude, x, z, sub_offset_x, mat, verts, faces, matids, uvs) + + def make_part(self, height, step_depth, part_x, part_z, x_move, x_offset, + z_offset, z_mode, steps_type, verts, faces, matids, uvs): + + params = [(stair.z_mode, stair.l_shape, stair.r_shape, + stair.bottom_z, stair.steps_type) for stair in self.stairs] + + for stair in self.stairs: + if x_offset > 0: + stair.l_shape = stair.r_shape + else: + stair.r_shape = stair.l_shape + stair.steps_type = steps_type + stair.z_mode = "LINEAR" + stair.bottom_z = part_z + if 'Curved' in type(stair).__name__: + stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = \ + stair.set_offset(x_move + x_offset + 0.5 * part_x, stair.l_shape) + stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = \ + stair.set_offset(x_move + x_offset - 0.5 * part_x, stair.r_shape) + else: + stair.l_line = stair.offset(x_move + x_offset + 0.5 * part_x) + stair.r_line = stair.offset(x_move + x_offset - 0.5 * part_x) + n_steps = self.n_steps(step_depth) + self.set_height(height / n_steps) + for j, stair in enumerate(self.stairs): + stair.z0 += z_offset + part_z + stair.n_step *= 2 + stair.t_step /= 2 + stair.step_height /= 2 + for i in range(stair.n_step): + stair.make_step(i, verts, faces, matids, uvs, nose_y=0) + stair.n_step /= 2 + stair.t_step *= 2 + stair.step_height *= 2 + stair.z_mode = params[j][0] + stair.l_shape = params[j][1] + stair.r_shape = params[j][2] + stair.bottom_z = params[j][3] + stair.steps_type = params[j][4] + stair.z0 -= z_offset + part_z + + def make_profile(self, profile, idmat, side, slice, height, step_depth, + x_offset, z_offset, extend, verts, faces, matids, uvs): + + for stair in self.stairs: + if 'Curved' in type(stair).__name__: + stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(-x_offset, stair.l_shape) + stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(x_offset, stair.r_shape) + else: + stair.l_line = stair.offset(-x_offset) + stair.r_line = stair.offset(x_offset) + + n_steps = self.n_steps(step_depth) + self.set_height(height / n_steps) + + n_stairs = len(self.stairs) - 1 + + if n_stairs < 0: + return + + sections = [] + sections.append([]) + + # first step + if extend != 0: + t = -extend / self.stairs[0].length + self.stairs[0].get_lerp_vect(sections[-1], side, 1, t, True) + + for s, stair in enumerate(self.stairs): + n_step = 1 + is_circle = False + + if 'Curved' in type(stair).__name__: + if side == "LEFT": + part = stair.l_arc + is_circle = stair.l_shape == "CIRCLE" + else: + part = stair.r_arc + is_circle = stair.r_shape == "CIRCLE" + else: + if side == "LEFT": + part = stair.l_line + else: + part = stair.r_line + + if is_circle: + n_step = 3 * stair.n_step + + t_step = 1 / n_step + + last_t = 1.0 + do_last = True + lerp_z = 0 + # last section 1 step before stair + if 'Landing' in type(stair).__name__ and stair.next_type == 'STAIR': + if not slice: + line = stair.normal(1).offset(self.stairs[s + 1].step_depth) + res, p, t_part = part.intersect(line) + # does perpendicular line intersects circle ? + if res: + last_t = 1 - self.stairs[s + 1].step_depth / stair.get_length(side) + if last_t < 0: + do_last = False + else: + # in this case, lerp z over one step + do_last = False + lerp_z = stair.step_height + + if s == n_stairs: + n_step += 1 + + for i in range(n_step): + res_t = stair.get_lerp_vect(sections[-1], side, i, t_step, True, z_offset=lerp_z) + # remove corner section + for cur_t in res_t: + if cur_t > 0 and cur_t > last_t: + sections[-1] = sections[-1][:-1] + + # last section 1 step before next stair start + if 'Landing' in type(stair).__name__ and stair.next_type == 'STAIR': + if do_last: + stair.get_lerp_vect(sections[-1], side, 1, last_t, False) + if slice: + sections.append([]) + if extend > 0: + t = -extend / self.stairs[s + 1].length + self.stairs[s + 1].get_lerp_vect(sections[-1], side, 1, t, True) + + t = 1 + extend / self.stairs[-1].length + self.stairs[-1].get_lerp_vect(sections[-1], side, 1, t, True) + + for cur_sect in sections: + user_path_verts = len(cur_sect) + f = len(verts) + if user_path_verts > 0: + user_path_uv_v = [] + n, dz, z0, z1 = cur_sect[-1] + cur_sect[-1] = (n, dz, z0 - stair.step_height, z1) + n_sections = user_path_verts - 1 + n, dz, zs, zl = cur_sect[0] + p0 = n.p + v0 = n.v.normalized() + for s, section in enumerate(cur_sect): + n, dz, zs, zl = section + p1 = n.p + if s < n_sections: + v1 = cur_sect[s + 1][0].v.normalized() + dir = (v0 + v1).normalized() + scale = 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1)))) + for p in profile: + x, y = n.p + scale * p.x * dir + z = zl + p.y + z_offset + verts.append((x, y, z)) + if s > 0: + user_path_uv_v.append((p1 - p0).length) + p0 = p1 + v0 = v1 + + # build faces using Panel + lofter = Lofter( + # closed_shape, index, x, y, idmat + True, + [i for i in range(len(profile))], + [p.x for p in profile], + [p.y for p in profile], + [idmat for i in range(len(profile))], + closed_path=False, + user_path_uv_v=user_path_uv_v, + user_path_verts=user_path_verts + ) + faces += lofter.faces(16, offset=f, path_type='USER_DEFINED') + matids += lofter.mat(16, idmat, idmat, path_type='USER_DEFINED') + v = Vector((0, 0)) + uvs += lofter.uv(16, v, v, v, v, 0, v, 0, 0, path_type='USER_DEFINED') + + def set_matids(self, id_materials): + for stair in self.stairs: + stair.set_matids(id_materials) + + +def update(self, context): + self.update(context) + + +def update_manipulators(self, context): + self.update(context, manipulable_refresh=True) + + +def update_preset(self, context): + auto_update = self.auto_update + self.auto_update = False + if self.presets == 'STAIR_I': + self.n_parts = 1 + self.update_parts() + self.parts[0].type = 'S_STAIR' + elif self.presets == 'STAIR_L': + self.n_parts = 3 + self.update_parts() + self.parts[0].type = 'S_STAIR' + self.parts[1].type = 'C_STAIR' + self.parts[2].type = 'S_STAIR' + self.da = pi / 2 + elif self.presets == 'STAIR_U': + self.n_parts = 3 + self.update_parts() + self.parts[0].type = 'S_STAIR' + self.parts[1].type = 'D_STAIR' + self.parts[2].type = 'S_STAIR' + self.da = pi + elif self.presets == 'STAIR_O': + self.n_parts = 2 + self.update_parts() + self.parts[0].type = 'D_STAIR' + self.parts[1].type = 'D_STAIR' + self.da = pi + # keep auto_update state same + # prevent unwanted load_preset update + self.auto_update = auto_update + + +materials_enum = ( + ('0', 'Ceiling', '', 0), + ('1', 'White', '', 1), + ('2', 'Concrete', '', 2), + ('3', 'Wood', '', 3), + ('4', 'Metal', '', 4), + ('5', 'Glass', '', 5) + ) + + +class archipack_stair_material(PropertyGroup): + index = EnumProperty( + items=materials_enum, + default='4', + update=update + ) + + def find_datablock_in_selection(self, context): + """ + find witch selected object this instance belongs to + provide support for "copy to selected" + """ + selected = [o for o in context.selected_objects] + for o in selected: + props = archipack_stair.datablock(o) + if props: + for part in props.rail_mat: + if part == self: + return props + return None + + def update(self, context): + props = self.find_datablock_in_selection(context) + if props is not None: + props.update(context) + + +class archipack_stair_part(PropertyGroup): + type = EnumProperty( + items=( + ('S_STAIR', 'Straight stair', '', 0), + ('C_STAIR', 'Curved stair', '', 1), + ('D_STAIR', 'Dual Curved stair', '', 2), + ('S_LANDING', 'Straight landing', '', 3), + ('C_LANDING', 'Curved landing', '', 4), + ('D_LANDING', 'Dual Curved landing', '', 5) + ), + default='S_STAIR', + update=update_manipulators + ) + length = FloatProperty( + name="length", + min=0.5, + default=2.0, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + radius = FloatProperty( + name="radius", + min=0.5, + default=0.7, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + da = FloatProperty( + name="angle", + min=-pi, + max=pi, + default=pi / 2, + subtype='ANGLE', unit='ROTATION', + update=update + ) + left_shape = EnumProperty( + items=( + ('RECTANGLE', 'Straight', '', 0), + ('CIRCLE', 'Curved ', '', 1) + ), + default='RECTANGLE', + update=update + ) + right_shape = EnumProperty( + items=( + ('RECTANGLE', 'Straight', '', 0), + ('CIRCLE', 'Curved ', '', 1) + ), + default='RECTANGLE', + update=update + ) + manipulators = CollectionProperty(type=archipack_manipulator) + + def find_datablock_in_selection(self, context): + """ + find witch selected object this instance belongs to + provide support for "copy to selected" + """ + selected = [o for o in context.selected_objects] + for o in selected: + props = archipack_stair.datablock(o) + if props: + for part in props.parts: + if part == self: + return props + return None + + def update(self, context, manipulable_refresh=False): + props = self.find_datablock_in_selection(context) + if props is not None: + props.update(context, manipulable_refresh) + + def draw(self, layout, context, index, user_mode): + if user_mode: + box = layout.box() + row = box.row() + row.prop(self, "type", text=str(index + 1)) + if self.type in ['C_STAIR', 'C_LANDING', 'D_STAIR', 'D_LANDING']: + row = box.row() + row.prop(self, "radius") + row = box.row() + row.prop(self, "da") + else: + row = box.row() + row.prop(self, "length") + if self.type in ['C_STAIR', 'C_LANDING', 'D_STAIR', 'D_LANDING']: + row = box.row(align=True) + row.prop(self, "left_shape", text="") + row.prop(self, "right_shape", text="") + else: + if self.type in ['S_STAIR', 'S_LANDING']: + box = layout.box() + row = box.row() + row.prop(self, "length") + + +class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): + + parts = CollectionProperty(type=archipack_stair_part) + n_parts = IntProperty( + name="parts", + min=1, + max=32, + default=1, update=update_manipulators + ) + step_depth = FloatProperty( + name="Going", + min=0.2, + default=0.25, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + width = FloatProperty( + name="width", + min=0.01, + default=1.2, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + height = FloatProperty( + name="Height", + min=0.1, + default=2.4, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + nose_y = FloatProperty( + name="Depth", + min=0.0, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + x_offset = FloatProperty( + name="x offset", + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + nose_z = FloatProperty( + name="Height", + min=0.001, + default=0.03, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + bottom_z = FloatProperty( + name="Stair bottom", + min=0.001, + default=0.03, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + radius = FloatProperty( + name="radius", + min=0.5, + default=0.7, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + da = FloatProperty( + name="angle", + min=-pi, + max=pi, + default=pi / 2, + subtype='ANGLE', unit='ROTATION', + update=update + ) + total_angle = FloatProperty( + name="angle", + min=-50 * pi, + max=50 * pi, + default=2 * pi, + subtype='ANGLE', unit='ROTATION', + update=update + ) + steps_type = EnumProperty( + name="Steps", + items=( + ('CLOSED', 'Closed', '', 0), + ('FULL', 'Full height', '', 1), + ('OPEN', 'Open ', '', 2) + ), + default='CLOSED', + update=update + ) + nose_type = EnumProperty( + name="Nosing", + items=( + ('STRAIGHT', 'Straight', '', 0), + ('OBLIQUE', 'Oblique', '', 1), + ), + default='STRAIGHT', + update=update + ) + left_shape = EnumProperty( + items=( + ('RECTANGLE', 'Straight', '', 0), + ('CIRCLE', 'Curved ', '', 1) + ), + default='RECTANGLE', + update=update + ) + right_shape = EnumProperty( + items=( + ('RECTANGLE', 'Straight', '', 0), + ('CIRCLE', 'Curved ', '', 1) + ), + default='RECTANGLE', + update=update + ) + z_mode = EnumProperty( + name="Interp z", + items=( + ('STANDARD', 'Standard', '', 0), + ('LINEAR', 'Bottom Linear', '', 1), + ('LINEAR_TOP', 'All Linear', '', 2) + ), + default='STANDARD', + update=update + ) + presets = EnumProperty( + items=( + ('STAIR_I', 'I stair', '', 0), + ('STAIR_L', 'L stair', '', 1), + ('STAIR_U', 'U stair', '', 2), + ('STAIR_O', 'O stair', '', 3), + ('STAIR_USER', 'User defined stair', '', 4), + ), + default='STAIR_I', update=update_preset + ) + left_post = BoolProperty( + name='left', + default=True, + update=update + ) + right_post = BoolProperty( + name='right', + default=True, + update=update + ) + post_spacing = FloatProperty( + name="spacing", + min=0.1, + default=1.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + post_x = FloatProperty( + name="width", + min=0.001, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + post_y = FloatProperty( + name="length", + min=0.001, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + post_z = FloatProperty( + name="height", + min=0.001, + default=1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + post_alt = FloatProperty( + name="altitude", + min=-100, + default=0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + post_offset_x = FloatProperty( + name="offset", + min=-100.0, max=100, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + post_corners = BoolProperty( + name="only on edges", + update=update, + default=False + ) + user_defined_post_enable = BoolProperty( + name="User", + update=update, + default=True + ) + user_defined_post = StringProperty( + name="user defined", + update=update + ) + idmat_post = EnumProperty( + name="Post", + items=materials_enum, + default='4', + update=update + ) + left_subs = BoolProperty( + name='left', + default=False, + update=update + ) + right_subs = BoolProperty( + name='right', + default=False, + update=update + ) + subs_spacing = FloatProperty( + name="spacing", + min=0.05, + default=0.10, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_x = FloatProperty( + name="width", + min=0.001, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_y = FloatProperty( + name="length", + min=0.001, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_z = FloatProperty( + name="height", + min=0.001, + default=1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_alt = FloatProperty( + name="altitude", + min=-100, + default=0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_offset_x = FloatProperty( + name="offset", + min=-100.0, max=100, + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_bottom = EnumProperty( + name="Bottom", + items=( + ('STEP', 'Follow step', '', 0), + ('LINEAR', 'Linear', '', 1), + ), + default='STEP', + update=update + ) + user_defined_subs_enable = BoolProperty( + name="User", + update=update, + default=True + ) + user_defined_subs = StringProperty( + name="user defined", + update=update + ) + idmat_subs = EnumProperty( + name="Subs", + items=materials_enum, + default='4', + update=update + ) + left_panel = BoolProperty( + name='left', + default=True, + update=update + ) + right_panel = BoolProperty( + name='right', + default=True, + update=update + ) + panel_alt = FloatProperty( + name="altitude", + default=0.25, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + panel_x = FloatProperty( + name="width", + min=0.001, + default=0.01, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + panel_z = FloatProperty( + name="height", + min=0.001, + default=0.6, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + panel_dist = FloatProperty( + name="space", + min=0.001, + default=0.05, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + panel_offset_x = FloatProperty( + name="offset", + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + idmat_panel = EnumProperty( + name="Panels", + items=materials_enum, + default='5', + update=update + ) + left_rail = BoolProperty( + name="left", + update=update, + default=False + ) + right_rail = BoolProperty( + name="right", + update=update, + default=False + ) + rail_n = IntProperty( + name="number", + default=1, + min=0, + max=31, + update=update + ) + rail_x = FloatVectorProperty( + name="width", + default=[ + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05 + ], + size=31, + min=0.001, + precision=2, step=1, + unit='LENGTH', + update=update + ) + rail_z = FloatVectorProperty( + name="height", + default=[ + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05 + ], + size=31, + min=0.001, + precision=2, step=1, + unit='LENGTH', + update=update + ) + rail_offset = FloatVectorProperty( + name="offset", + default=[ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0 + ], + size=31, + precision=2, step=1, + unit='LENGTH', + update=update + ) + rail_alt = FloatVectorProperty( + name="altitude", + default=[ + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 + ], + size=31, + precision=2, step=1, + unit='LENGTH', + update=update + ) + rail_mat = CollectionProperty(type=archipack_stair_material) + + left_handrail = BoolProperty( + name="left", + update=update, + default=True + ) + right_handrail = BoolProperty( + name="right", + update=update, + default=True + ) + handrail_offset = FloatProperty( + name="offset", + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_alt = FloatProperty( + name="altitude", + default=1.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_extend = FloatProperty( + name="extend", + default=0.1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_slice_left = BoolProperty( + name='slice', + default=True, + update=update + ) + handrail_slice_right = BoolProperty( + name='slice', + default=True, + update=update + ) + handrail_profil = EnumProperty( + name="Profil", + items=( + ('SQUARE', 'Square', '', 0), + ('CIRCLE', 'Circle', '', 1), + ('COMPLEX', 'Circle over square', '', 2) + ), + default='SQUARE', + update=update + ) + handrail_x = FloatProperty( + name="width", + min=0.001, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_y = FloatProperty( + name="height", + min=0.001, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_radius = FloatProperty( + name="radius", + min=0.001, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + + left_string = BoolProperty( + name="left", + update=update, + default=False + ) + right_string = BoolProperty( + name="right", + update=update, + default=False + ) + string_x = FloatProperty( + name="width", + min=-100.0, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + string_z = FloatProperty( + name="height", + default=0.3, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + string_offset = FloatProperty( + name="offset", + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + string_alt = FloatProperty( + name="altitude", + default=-0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + + idmat_bottom = EnumProperty( + name="Bottom", + items=materials_enum, + default='1', + update=update + ) + idmat_raise = EnumProperty( + name="Raise", + items=materials_enum, + default='1', + update=update + ) + idmat_step_front = EnumProperty( + name="Step front", + items=materials_enum, + default='3', + update=update + ) + idmat_top = EnumProperty( + name="Top", + items=materials_enum, + default='3', + update=update + ) + idmat_side = EnumProperty( + name="Side", + items=materials_enum, + default='1', + update=update + ) + idmat_step_side = EnumProperty( + name="Step Side", + items=materials_enum, + default='3', + update=update + ) + idmat_handrail = EnumProperty( + name="Handrail", + items=materials_enum, + default='3', + update=update + ) + idmat_string = EnumProperty( + name="String", + items=materials_enum, + default='3', + update=update + ) + + # UI layout related + parts_expand = BoolProperty( + default=False + ) + steps_expand = BoolProperty( + default=False + ) + rail_expand = BoolProperty( + default=False + ) + idmats_expand = BoolProperty( + default=False + ) + handrail_expand = BoolProperty( + default=False + ) + string_expand = BoolProperty( + default=False + ) + post_expand = BoolProperty( + default=False + ) + panel_expand = BoolProperty( + default=False + ) + subs_expand = BoolProperty( + default=False + ) + + auto_update = BoolProperty( + options={'SKIP_SAVE'}, + default=True, + update=update_manipulators + ) + + def setup_manipulators(self): + + if len(self.manipulators) == 0: + s = self.manipulators.add() + s.prop1_name = "width" + s = self.manipulators.add() + s.prop1_name = "height" + s.normal = Vector((0, 1, 0)) + + for i in range(self.n_parts): + p = self.parts[i] + n_manips = len(p.manipulators) + if n_manips < 1: + m = p.manipulators.add() + m.type_key = 'SIZE' + m.prop1_name = 'length' + + def update_parts(self): + + # remove rails materials + for i in range(len(self.rail_mat), self.rail_n, -1): + self.rail_mat.remove(i - 1) + + # add rails + for i in range(len(self.rail_mat), self.rail_n): + self.rail_mat.add() + + # remove parts + for i in range(len(self.parts), self.n_parts, -1): + self.parts.remove(i - 1) + + # add parts + for i in range(len(self.parts), self.n_parts): + self.parts.add() + + self.setup_manipulators() + + def update(self, context, manipulable_refresh=False): + + o = self.find_in_selection(context, self.auto_update) + + if o is None: + return + + # clean up manipulators before any data model change + if manipulable_refresh: + self.manipulable_disable(context) + + self.update_parts() + + center = Vector((0, 0)) + verts = [] + faces = [] + matids = [] + uvs = [] + id_materials = [int(self.idmat_top), int(self.idmat_step_front), int(self.idmat_raise), + int(self.idmat_side), int(self.idmat_bottom), int(self.idmat_step_side)] + + # depth at bottom + bottom_z = self.bottom_z + if self.steps_type == 'OPEN': + # depth at front + bottom_z = self.nose_z + + width_left = 0.5 * self.width - self.x_offset + width_right = 0.5 * self.width + self.x_offset + + self.manipulators[0].set_pts([(-width_left, 0, 0), (width_right, 0, 0), (1, 0, 0)]) + self.manipulators[1].set_pts([(0, 0, 0), (0, 0, self.height), (1, 0, 0)]) + + g = StairGenerator(self.parts) + if self.presets == 'STAIR_USER': + for part in self.parts: + g.add_part(part.type, self.steps_type, self.nose_type, self.z_mode, self.nose_z, + bottom_z, center, max(width_left + 0.01, width_right + 0.01, part.radius), part.da, + width_left, width_right, part.length, part.left_shape, part.right_shape) + + elif self.presets == 'STAIR_O': + n_parts = max(1, int(round(abs(self.total_angle) / pi, 0))) + if self.total_angle > 0: + dir = 1 + else: + dir = -1 + last_da = self.total_angle - dir * (n_parts - 1) * pi + if dir * last_da > pi: + n_parts += 1 + last_da -= dir * pi + abs_last = dir * last_da + + for part in range(n_parts - 1): + g.add_part('D_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z, + bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius), dir * pi, + width_left, width_right, 1.0, self.left_shape, self.right_shape) + if round(abs_last, 2) > 0: + if abs_last > pi / 2: + g.add_part('C_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z, + bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius), + dir * pi / 2, + width_left, width_right, 1.0, self.left_shape, self.right_shape) + g.add_part('C_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z, + bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius), + last_da - dir * pi / 2, + width_left, width_right, 1.0, self.left_shape, self.right_shape) + else: + g.add_part('C_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z, + bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius), last_da, + width_left, width_right, 1.0, self.left_shape, self.right_shape) + else: + # STAIR_L STAIR_I STAIR_U + for part in self.parts: + g.add_part(part.type, self.steps_type, self.nose_type, self.z_mode, self.nose_z, + bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius), self.da, + width_left, width_right, part.length, self.left_shape, self.right_shape) + + # Stair basis + g.set_matids(id_materials) + g.make_stair(self.height, self.step_depth, verts, faces, matids, uvs, nose_y=self.nose_y) + + # Ladder + offset_x = 0.5 * self.width - self.post_offset_x + post_spacing = self.post_spacing + if self.post_corners: + post_spacing = 10000 + + if self.user_defined_post_enable: + # user defined posts + user_def_post = context.scene.objects.get(self.user_defined_post) + if user_def_post is not None and user_def_post.type == 'MESH': + g.setup_user_defined_post(user_def_post, self.post_x, self.post_y, self.post_z) + + if self.left_post: + g.make_post(self.height, self.step_depth, 0.5 * self.post_x, 0.5 * self.post_y, + self.post_z, self.post_alt, 'LEFT', post_spacing, self.post_corners, + self.x_offset, offset_x, int(self.idmat_post), verts, faces, matids, uvs) + + if self.right_post: + g.make_post(self.height, self.step_depth, 0.5 * self.post_x, 0.5 * self.post_y, + self.post_z, self.post_alt, 'RIGHT', post_spacing, self.post_corners, + self.x_offset, offset_x, int(self.idmat_post), verts, faces, matids, uvs) + + # reset user def posts + g.user_defined_post = None + + # user defined subs + if self.user_defined_subs_enable: + user_def_subs = context.scene.objects.get(self.user_defined_subs) + if user_def_subs is not None and user_def_subs.type == 'MESH': + g.setup_user_defined_post(user_def_subs, self.subs_x, self.subs_y, self.subs_z) + + if self.left_subs: + g.make_subs(self.height, self.step_depth, 0.5 * self.subs_x, 0.5 * self.subs_y, + self.subs_z, 0.5 * self.post_y, self.subs_alt, self.subs_bottom, 'LEFT', + self.handrail_slice_left, post_spacing, self.subs_spacing, self.post_corners, + self.x_offset, offset_x, -self.subs_offset_x, int(self.idmat_subs), verts, faces, matids, uvs) + + if self.right_subs: + g.make_subs(self.height, self.step_depth, 0.5 * self.subs_x, 0.5 * self.subs_y, + self.subs_z, 0.5 * self.post_y, self.subs_alt, self.subs_bottom, 'RIGHT', + self.handrail_slice_right, post_spacing, self.subs_spacing, self.post_corners, + self.x_offset, offset_x, self.subs_offset_x, int(self.idmat_subs), verts, faces, matids, uvs) + + g.user_defined_post = None + + if self.left_panel: + g.make_panels(self.height, self.step_depth, 0.5 * self.panel_x, self.panel_z, 0.5 * self.post_y, + self.panel_alt, 'LEFT', post_spacing, self.panel_dist, self.post_corners, + self.x_offset, offset_x, -self.panel_offset_x, int(self.idmat_panel), verts, faces, matids, uvs) + + if self.right_panel: + g.make_panels(self.height, self.step_depth, 0.5 * self.panel_x, self.panel_z, 0.5 * self.post_y, + self.panel_alt, 'RIGHT', post_spacing, self.panel_dist, self.post_corners, + self.x_offset, offset_x, self.panel_offset_x, int(self.idmat_panel), verts, faces, matids, uvs) + + if self.right_rail: + for i in range(self.rail_n): + id_materials = [int(self.rail_mat[i].index) for j in range(6)] + g.set_matids(id_materials) + g.make_part(self.height, self.step_depth, self.rail_x[i], self.rail_z[i], + self.x_offset, offset_x + self.rail_offset[i], + self.rail_alt[i], 'LINEAR', 'CLOSED', verts, faces, matids, uvs) + + if self.left_rail: + for i in range(self.rail_n): + id_materials = [int(self.rail_mat[i].index) for j in range(6)] + g.set_matids(id_materials) + g.make_part(self.height, self.step_depth, self.rail_x[i], self.rail_z[i], + self.x_offset, -offset_x - self.rail_offset[i], + self.rail_alt[i], 'LINEAR', 'CLOSED', verts, faces, matids, uvs) + + if self.handrail_profil == 'COMPLEX': + sx = self.handrail_x + sy = self.handrail_y + handrail = [Vector((sx * x, sy * y)) for x, y in [ + (-0.28, 1.83), (-0.355, 1.77), (-0.415, 1.695), (-0.46, 1.605), (-0.49, 1.51), (-0.5, 1.415), + (-0.49, 1.315), (-0.46, 1.225), (-0.415, 1.135), (-0.355, 1.06), (-0.28, 1.0), (-0.255, 0.925), + (-0.33, 0.855), (-0.5, 0.855), (-0.5, 0.0), (0.5, 0.0), (0.5, 0.855), (0.33, 0.855), (0.255, 0.925), + (0.28, 1.0), (0.355, 1.06), (0.415, 1.135), (0.46, 1.225), (0.49, 1.315), (0.5, 1.415), + (0.49, 1.51), (0.46, 1.605), (0.415, 1.695), (0.355, 1.77), (0.28, 1.83), (0.19, 1.875), + (0.1, 1.905), (0.0, 1.915), (-0.095, 1.905), (-0.19, 1.875)]] + + elif self.handrail_profil == 'SQUARE': + x = 0.5 * self.handrail_x + y = self.handrail_y + handrail = [Vector((-x, y)), Vector((-x, 0)), Vector((x, 0)), Vector((x, y))] + elif self.handrail_profil == 'CIRCLE': + r = self.handrail_radius + handrail = [Vector((r * sin(0.1 * -a * pi), r * (0.5 + cos(0.1 * -a * pi)))) for a in range(0, 20)] + + if self.right_handrail: + g.make_profile(handrail, int(self.idmat_handrail), "RIGHT", self.handrail_slice_right, + self.height, self.step_depth, self.x_offset + offset_x + self.handrail_offset, + self.handrail_alt, self.handrail_extend, verts, faces, matids, uvs) + + if self.left_handrail: + g.make_profile(handrail, int(self.idmat_handrail), "LEFT", self.handrail_slice_left, + self.height, self.step_depth, -self.x_offset + offset_x + self.handrail_offset, + self.handrail_alt, self.handrail_extend, verts, faces, matids, uvs) + + w = 0.5 * self.string_x + h = self.string_z + string = [Vector((-w, 0)), Vector((w, 0)), Vector((w, h)), Vector((-w, h))] + + if self.right_string: + g.make_profile(string, int(self.idmat_string), "RIGHT", False, self.height, self.step_depth, + self.x_offset + 0.5 * self.width + self.string_offset, + self.string_alt, 0, verts, faces, matids, uvs) + + if self.left_string: + g.make_profile(string, int(self.idmat_string), "LEFT", False, self.height, self.step_depth, + -self.x_offset + 0.5 * self.width + self.string_offset, + self.string_alt, 0, verts, faces, matids, uvs) + + bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs, weld=True, clean=True) + + # enable manipulators rebuild + if manipulable_refresh: + self.manipulable_refresh = True + + self.restore_context(context) + + def manipulable_setup(self, context): + """ + TODO: Implement the setup part as per parent object basis + + self.manipulable_disable(context) + o = context.active_object + for m in self.manipulators: + self.manip_stack.append(m.setup(context, o, self)) + + """ + self.manipulable_disable(context) + o = context.active_object + + self.setup_manipulators() + + if self.presets is not 'STAIR_O': + for i, part in enumerate(self.parts): + if i >= self.n_parts: + break + if "S_" in part.type or self.presets in ['STAIR_USER']: + for j, m in enumerate(part.manipulators): + self.manip_stack.append(m.setup(context, o, part)) + + if self.presets in ['STAIR_U', 'STAIR_L']: + self.manip_stack.append(self.parts[1].manipulators[0].setup(context, o, self)) + + for m in self.manipulators: + self.manip_stack.append(m.setup(context, o, self)) + + +class ARCHIPACK_PT_stair(Panel): + bl_idname = "ARCHIPACK_PT_stair" + bl_label = "Stair" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + # bl_context = 'object' + bl_category = 'ArchiPack' + + @classmethod + def poll(cls, context): + return archipack_stair.filter(context.active_object) + + def draw(self, context): + prop = archipack_stair.datablock(context.active_object) + if prop is None: + return + scene = context.scene + layout = self.layout + row = layout.row(align=True) + row.operator('archipack.stair_manipulate', icon='HAND') + row = layout.row(align=True) + row.prop(prop, 'presets', text="") + box = layout.box() + # box.label(text="Styles") + row = box.row(align=True) + # row.menu("ARCHIPACK_MT_stair_preset", text=bpy.types.ARCHIPACK_MT_stair_preset.bl_label) + row.operator("archipack.stair_preset_menu", text=bpy.types.ARCHIPACK_OT_stair_preset_menu.bl_label) + row.operator("archipack.stair_preset", text="", icon='ZOOMIN') + row.operator("archipack.stair_preset", text="", icon='ZOOMOUT').remove_active = True + box = layout.box() + box.prop(prop, 'width') + box.prop(prop, 'height') + box.prop(prop, 'bottom_z') + box.prop(prop, 'x_offset') + # box.prop(prop, 'z_mode') + box = layout.box() + row = box.row() + if prop.parts_expand: + row.prop(prop, 'parts_expand', icon="TRIA_DOWN", icon_only=True, text="Parts", emboss=False) + if prop.presets == 'STAIR_USER': + box.prop(prop, 'n_parts') + if prop.presets != 'STAIR_USER': + row = box.row(align=True) + row.prop(prop, "left_shape", text="") + row.prop(prop, "right_shape", text="") + row = box.row() + row.prop(prop, "radius") + row = box.row() + if prop.presets == 'STAIR_O': + row.prop(prop, 'total_angle') + else: + row.prop(prop, 'da') + if prop.presets != 'STAIR_O': + for i, part in enumerate(prop.parts): + part.draw(layout, context, i, prop.presets == 'STAIR_USER') + else: + row.prop(prop, 'parts_expand', icon="TRIA_RIGHT", icon_only=True, text="Parts", emboss=False) + + box = layout.box() + row = box.row() + if prop.steps_expand: + row.prop(prop, 'steps_expand', icon="TRIA_DOWN", icon_only=True, text="Steps", emboss=False) + box.prop(prop, 'steps_type') + box.prop(prop, 'step_depth') + box.prop(prop, 'nose_type') + box.prop(prop, 'nose_z') + box.prop(prop, 'nose_y') + else: + row.prop(prop, 'steps_expand', icon="TRIA_RIGHT", icon_only=True, text="Steps", emboss=False) + + box = layout.box() + row = box.row(align=True) + if prop.handrail_expand: + row.prop(prop, 'handrail_expand', icon="TRIA_DOWN", icon_only=True, text="Handrail", emboss=False) + else: + row.prop(prop, 'handrail_expand', icon="TRIA_RIGHT", icon_only=True, text="Handrail", emboss=False) + + row.prop(prop, 'left_handrail') + row.prop(prop, 'right_handrail') + + if prop.handrail_expand: + box.prop(prop, 'handrail_alt') + box.prop(prop, 'handrail_offset') + box.prop(prop, 'handrail_extend') + box.prop(prop, 'handrail_profil') + if prop.handrail_profil != 'CIRCLE': + box.prop(prop, 'handrail_x') + box.prop(prop, 'handrail_y') + else: + box.prop(prop, 'handrail_radius') + row = box.row(align=True) + row.prop(prop, 'handrail_slice_left') + row.prop(prop, 'handrail_slice_right') + + box = layout.box() + row = box.row(align=True) + if prop.string_expand: + row.prop(prop, 'string_expand', icon="TRIA_DOWN", icon_only=True, text="String", emboss=False) + else: + row.prop(prop, 'string_expand', icon="TRIA_RIGHT", icon_only=True, text="String", emboss=False) + row.prop(prop, 'left_string') + row.prop(prop, 'right_string') + if prop.string_expand: + box.prop(prop, 'string_x') + box.prop(prop, 'string_z') + box.prop(prop, 'string_alt') + box.prop(prop, 'string_offset') + + box = layout.box() + row = box.row(align=True) + if prop.post_expand: + row.prop(prop, 'post_expand', icon="TRIA_DOWN", icon_only=True, text="Post", emboss=False) + else: + row.prop(prop, 'post_expand', icon="TRIA_RIGHT", icon_only=True, text="Post", emboss=False) + row.prop(prop, 'left_post') + row.prop(prop, 'right_post') + if prop.post_expand: + box.prop(prop, 'post_corners') + if not prop.post_corners: + box.prop(prop, 'post_spacing') + box.prop(prop, 'post_x') + box.prop(prop, 'post_y') + box.prop(prop, 'post_z') + box.prop(prop, 'post_alt') + box.prop(prop, 'post_offset_x') + row = box.row(align=True) + row.prop(prop, 'user_defined_post_enable', text="") + row.prop_search(prop, "user_defined_post", scene, "objects", text="") + + box = layout.box() + row = box.row(align=True) + if prop.subs_expand: + row.prop(prop, 'subs_expand', icon="TRIA_DOWN", icon_only=True, text="Subs", emboss=False) + else: + row.prop(prop, 'subs_expand', icon="TRIA_RIGHT", icon_only=True, text="Subs", emboss=False) + + row.prop(prop, 'left_subs') + row.prop(prop, 'right_subs') + if prop.subs_expand: + box.prop(prop, 'subs_spacing') + box.prop(prop, 'subs_x') + box.prop(prop, 'subs_y') + box.prop(prop, 'subs_z') + box.prop(prop, 'subs_alt') + box.prop(prop, 'subs_offset_x') + box.prop(prop, 'subs_bottom') + row = box.row(align=True) + row.prop(prop, 'user_defined_subs_enable', text="") + row.prop_search(prop, "user_defined_subs", scene, "objects", text="") + + box = layout.box() + row = box.row(align=True) + if prop.panel_expand: + row.prop(prop, 'panel_expand', icon="TRIA_DOWN", icon_only=True, text="Panels", emboss=False) + else: + row.prop(prop, 'panel_expand', icon="TRIA_RIGHT", icon_only=True, text="Panels", emboss=False) + row.prop(prop, 'left_panel') + row.prop(prop, 'right_panel') + if prop.panel_expand: + box.prop(prop, 'panel_dist') + box.prop(prop, 'panel_x') + box.prop(prop, 'panel_z') + box.prop(prop, 'panel_alt') + box.prop(prop, 'panel_offset_x') + + box = layout.box() + row = box.row(align=True) + if prop.rail_expand: + row.prop(prop, 'rail_expand', icon="TRIA_DOWN", icon_only=True, text="Rails", emboss=False) + else: + row.prop(prop, 'rail_expand', icon="TRIA_RIGHT", icon_only=True, text="Rails", emboss=False) + row.prop(prop, 'left_rail') + row.prop(prop, 'right_rail') + if prop.rail_expand: + box.prop(prop, 'rail_n') + for i in range(prop.rail_n): + box = layout.box() + box.label(text="Rail " + str(i + 1)) + box.prop(prop, 'rail_x', index=i) + box.prop(prop, 'rail_z', index=i) + box.prop(prop, 'rail_alt', index=i) + box.prop(prop, 'rail_offset', index=i) + box.prop(prop.rail_mat[i], 'index', text="") + + box = layout.box() + row = box.row() + + if prop.idmats_expand: + row.prop(prop, 'idmats_expand', icon="TRIA_DOWN", icon_only=True, text="Materials", emboss=False) + box.prop(prop, 'idmat_top') + box.prop(prop, 'idmat_side') + box.prop(prop, 'idmat_bottom') + box.prop(prop, 'idmat_step_side') + box.prop(prop, 'idmat_step_front') + box.prop(prop, 'idmat_raise') + box.prop(prop, 'idmat_handrail') + box.prop(prop, 'idmat_panel') + box.prop(prop, 'idmat_post') + box.prop(prop, 'idmat_subs') + box.prop(prop, 'idmat_string') + else: + row.prop(prop, 'idmats_expand', icon="TRIA_RIGHT", icon_only=True, text="Materials", emboss=False) + + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_stair(ArchipackCreateTool, Operator): + bl_idname = "archipack.stair" + bl_label = "Stair" + bl_description = "Create a Stair" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + def create(self, context): + m = bpy.data.meshes.new("Stair") + o = bpy.data.objects.new("Stair", m) + d = m.archipack_stair.add() + context.scene.objects.link(o) + o.select = True + context.scene.objects.active = o + self.load_preset(d) + self.add_material(o) + m.auto_smooth_angle = 0.20944 + return o + + # ----------------------------------------------------- + # Execute + # ----------------------------------------------------- + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.location = context.scene.cursor_location + o.select = True + context.scene.objects.active = o + self.manipulate() + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + +# ------------------------------------------------------------------ +# Define operator class to manipulate object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_stair_manipulate(Operator): + bl_idname = "archipack.stair_manipulate" + bl_label = "Manipulate" + bl_description = "Manipulate" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_stair.filter(context.active_object) + + def invoke(self, context, event): + d = archipack_stair.datablock(context.active_object) + d.manipulable_invoke(context) + return {'FINISHED'} + + +# ------------------------------------------------------------------ +# Define operator class to load / save presets +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_stair_preset_menu(PresetMenuOperator, Operator): + bl_idname = "archipack.stair_preset_menu" + bl_label = "Stair style" + preset_subdir = "archipack_stair" + + +class ARCHIPACK_OT_stair_preset(ArchipackPreset, Operator): + """Add a Stair Preset""" + bl_idname = "archipack.stair_preset" + bl_label = "Add Stair Style" + preset_menu = "ARCHIPACK_OT_stair_preset_menu" + + @property + def blacklist(self): + return ['manipulators'] + + """ + 'presets', 'n_parts', 'parts', 'width', 'height', 'radius', + 'total_angle', 'da', + """ + + +def register(): + bpy.utils.register_class(archipack_stair_material) + bpy.utils.register_class(archipack_stair_part) + bpy.utils.register_class(archipack_stair) + Mesh.archipack_stair = CollectionProperty(type=archipack_stair) + bpy.utils.register_class(ARCHIPACK_PT_stair) + bpy.utils.register_class(ARCHIPACK_OT_stair) + bpy.utils.register_class(ARCHIPACK_OT_stair_preset_menu) + bpy.utils.register_class(ARCHIPACK_OT_stair_preset) + bpy.utils.register_class(ARCHIPACK_OT_stair_manipulate) + + +def unregister(): + bpy.utils.unregister_class(archipack_stair_material) + bpy.utils.unregister_class(archipack_stair_part) + bpy.utils.unregister_class(archipack_stair) + del Mesh.archipack_stair + bpy.utils.unregister_class(ARCHIPACK_PT_stair) + bpy.utils.unregister_class(ARCHIPACK_OT_stair) + bpy.utils.unregister_class(ARCHIPACK_OT_stair_preset_menu) + bpy.utils.unregister_class(ARCHIPACK_OT_stair_preset) + bpy.utils.unregister_class(ARCHIPACK_OT_stair_manipulate) diff --git a/archipack/archipack_truss.py b/archipack/archipack_truss.py new file mode 100644 index 000000000..b8056daab --- /dev/null +++ b/archipack/archipack_truss.py @@ -0,0 +1,380 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +import bpy +from bpy.types import Operator, PropertyGroup, Mesh, Panel +from bpy.props import ( + FloatProperty, IntProperty, BoolProperty, + CollectionProperty, EnumProperty +) +from .bmesh_utils import BmeshEdit as bmed +# from .materialutils import MaterialUtils +from mathutils import Vector, Matrix +from math import sin, cos, pi +from .archipack_manipulator import Manipulable +from .archipack_object import ArchipackCreateTool, ArchipackObject + + +def update(self, context): + self.update(context) + + +class archipack_truss(ArchipackObject, Manipulable, PropertyGroup): + truss_type = EnumProperty( + name="Type", + items=( + ('1', 'Prolyte E20', 'Prolyte E20', 0), + ('2', 'Prolyte X30', 'Prolyte X30', 1), + ('3', 'Prolyte H30', 'Prolyte H30', 2), + ('4', 'Prolyte H40', 'Prolyte H40', 3), + ('5', 'OPTI Trilite 100', 'OPTI Trilite 100', 4), + ('6', 'OPTI Trilite 200', 'OPTI Trilite 200', 5), + ('7', 'User defined', 'User defined', 6) + ), + default='2', + update=update + ) + z = FloatProperty( + name="Height", + default=2.0, min=0.01, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + segs = IntProperty( + name="Segs", + default=6, min=3, + update=update + ) + master_segs = IntProperty( + name="Master Segs", + default=1, min=1, + update=update + ) + master_count = IntProperty( + name="Masters", + default=3, min=2, + update=update + ) + entre_axe = FloatProperty( + name="Distance", + default=0.239, min=0.001, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + master_radius = FloatProperty( + name="Radius", + default=0.02415, min=0.0001, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + slaves_radius = FloatProperty( + name="Subs radius", + default=0.01, min=0.0001, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + # Flag to prevent mesh update while making bulk changes over variables + # use : + # .auto_update = False + # bulk changes + # .auto_update = True + auto_update = BoolProperty( + options={'SKIP_SAVE'}, + default=True, + update=update + ) + + def setup_manipulators(self): + if len(self.manipulators) < 1: + s = self.manipulators.add() + s.prop1_name = "z" + s.type_key = 'SIZE' + s.normal = Vector((0, 1, 0)) + + def docylinder(self, faces, verts, radius, segs, tMt, tMb, tM, add=False): + segs_step = 2 * pi / segs + tmpverts = [0 for i in range(segs)] + if add: + cv = len(verts) - segs + else: + cv = len(verts) + for seg in range(segs): + seg_angle = pi / 4 + seg * segs_step + tmpverts[seg] = radius * Vector((sin(seg_angle), -cos(seg_angle), 0)) + + if not add: + for seg in range(segs): + verts.append(tM * tMb * tmpverts[seg]) + + for seg in range(segs): + verts.append(tM * tMt * tmpverts[seg]) + + for seg in range(segs - 1): + f = cv + seg + faces.append((f + 1, f, f + segs, f + segs + 1)) + f = cv + faces.append((f, f + segs - 1, f + 2 * segs - 1, f + segs)) + + def update(self, context): + + o = self.find_in_selection(context, self.auto_update) + + if o is None: + return + + self.setup_manipulators() + + if self.truss_type == '1': + EntreAxe = 0.19 + master_radius = 0.016 + slaves_radius = 0.005 + elif self.truss_type == '2': + EntreAxe = 0.239 + master_radius = 0.0255 + slaves_radius = 0.008 + elif self.truss_type == '3': + EntreAxe = 0.239 + master_radius = 0.02415 + slaves_radius = 0.008 + elif self.truss_type == '4': + EntreAxe = 0.339 + master_radius = 0.02415 + slaves_radius = 0.01 + elif self.truss_type == '5': + EntreAxe = 0.15 + master_radius = 0.0127 + slaves_radius = 0.004 + elif self.truss_type == '6': + EntreAxe = 0.200 + master_radius = 0.0254 + slaves_radius = 0.00635 + elif self.truss_type == '7': + EntreAxe = self.entre_axe + master_radius = min(0.5 * self.entre_axe, self.master_radius) + slaves_radius = min(0.5 * self.entre_axe, self.master_radius, self.slaves_radius) + + master_sepang = (pi * (self.master_count - 2) / self.master_count) / 2 + radius = (EntreAxe / 2) / cos(master_sepang) + master_step = pi * 2 / self.master_count + + verts = [] + faces = [] + + if self.master_count == 4: + master_rotation = pi / 4 # 45.0 + else: + master_rotation = 0.0 + + slaves_width = 2 * radius * sin(master_step / 2) + slaves_count = int(self.z / slaves_width) + slave_firstOffset = (self.z - slaves_count * slaves_width) / 2 + master_z = self.z / self.master_segs + + for master in range(self.master_count): + + master_angle = master_rotation + master * master_step + + tM = Matrix([ + [1, 0, 0, radius * sin(master_angle)], + [0, 1, 0, radius * -cos(master_angle)], + [0, 0, 1, 0], + [0, 0, 0, 1]]) + + tMb = Matrix([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, self.z], + [0, 0, 0, 1]]) + + for n in range(1, self.master_segs + 1): + tMt = Matrix([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, self.z - n * master_z], + [0, 0, 0, 1]]) + self.docylinder(faces, verts, master_radius, self.segs, tMt, tMb, tM, add=(n > 1)) + + if self.master_count < 3 and master == 1: + continue + + ma = master_angle + master_sepang + + tM = Matrix([ + [cos(ma), sin(ma), 0, radius * sin(master_angle)], + [sin(ma), -cos(ma), 0, radius * -cos(master_angle)], + [0, 0, 1, slave_firstOffset], + [0, 0, 0, 1]]) + + if int(self.truss_type) < 5: + tMb = Matrix([ + [1, 0, 0, 0], + [0, 0, 1, 0], + [0, 1, 0, 0], + [0, 0, 0, 1]]) + tMt = Matrix([ + [1, 0, 0, 0], + [0, 0, 1, -slaves_width], + [0, 1, 0, 0], + [0, 0, 0, 1]]) + self.docylinder(faces, verts, slaves_radius, self.segs, tMt, tMb, tM) + + tMb = Matrix([ + [1, 0, 0, 0], + [0, 1.4142, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1]]) + + for n in range(1, slaves_count + 1): + tMt = Matrix([ + [1, 0, 0, 0], + [0, 1.4142, 0, -(n % 2) * slaves_width], + [0, 0, 1, n * slaves_width], + [0, 0, 0, 1]]) + self.docylinder(faces, verts, slaves_radius, self.segs, tMt, tMb, tM, add=(n > 1)) + + if int(self.truss_type) < 5: + tMb = Matrix([ + [1, 0, 0, 0], + [0, 0, 1, 0], + [0, 1, 0, slaves_count * slaves_width], + [0, 0, 0, 1]]) + tMt = Matrix([ + [1, 0, 0, 0], + [0, 0, 1, -slaves_width], + [0, 1, 0, slaves_count * slaves_width], + [0, 0, 0, 1]]) + self.docylinder(faces, verts, slaves_radius, self.segs, tMt, tMb, tM) + + bmed.buildmesh(context, o, verts, faces, matids=None, uvs=None, weld=False) + self.manipulators[0].set_pts([(0, 0, 0), (0, 0, self.z), (1, 0, 0)]) + + self.restore_context(context) + + +class ARCHIPACK_PT_truss(Panel): + """Archipack Truss""" + bl_idname = "ARCHIPACK_PT_truss" + bl_label = "Truss" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'ArchiPack' + + @classmethod + def poll(cls, context): + return archipack_truss.filter(context.active_object) + + def draw(self, context): + prop = archipack_truss.datablock(context.active_object) + if prop is None: + return + layout = self.layout + row = layout.row(align=True) + row.operator('archipack.truss_manipulate', icon='HAND') + box = layout.box() + box.prop(prop, 'truss_type') + box.prop(prop, 'z') + box.prop(prop, 'segs') + box.prop(prop, 'master_segs') + box.prop(prop, 'master_count') + if prop.truss_type == '7': + box.prop(prop, 'master_radius') + box.prop(prop, 'slaves_radius') + box.prop(prop, 'entre_axe') + + +class ARCHIPACK_OT_truss(ArchipackCreateTool, Operator): + bl_idname = "archipack.truss" + bl_label = "Truss" + bl_description = "Create Truss" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + def create(self, context): + m = bpy.data.meshes.new("Truss") + o = bpy.data.objects.new("Truss", m) + d = m.archipack_truss.add() + # make manipulators selectable + # d.manipulable_selectable = True + context.scene.objects.link(o) + o.select = True + context.scene.objects.active = o + self.load_preset(d) + self.add_material(o) + m.auto_smooth_angle = 1.15 + return o + + # ----------------------------------------------------- + # Execute + # ----------------------------------------------------- + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.location = bpy.context.scene.cursor_location + o.select = True + context.scene.objects.active = o + self.manipulate() + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +# ------------------------------------------------------------------ +# Define operator class to manipulate object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_truss_manipulate(Operator): + bl_idname = "archipack.truss_manipulate" + bl_label = "Manipulate" + bl_description = "Manipulate" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_truss.filter(context.active_object) + + def invoke(self, context, event): + d = archipack_truss.datablock(context.active_object) + d.manipulable_invoke(context) + return {'FINISHED'} + + +def register(): + bpy.utils.register_class(archipack_truss) + Mesh.archipack_truss = CollectionProperty(type=archipack_truss) + bpy.utils.register_class(ARCHIPACK_PT_truss) + bpy.utils.register_class(ARCHIPACK_OT_truss) + bpy.utils.register_class(ARCHIPACK_OT_truss_manipulate) + + +def unregister(): + bpy.utils.unregister_class(archipack_truss) + del Mesh.archipack_truss + bpy.utils.unregister_class(ARCHIPACK_PT_truss) + bpy.utils.unregister_class(ARCHIPACK_OT_truss) + bpy.utils.unregister_class(ARCHIPACK_OT_truss_manipulate) diff --git a/archipack/archipack_wall.py b/archipack/archipack_wall.py new file mode 100644 index 000000000..5adf92c2e --- /dev/null +++ b/archipack/archipack_wall.py @@ -0,0 +1,137 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +import bpy +import bmesh +from bpy.types import Operator, PropertyGroup, Mesh, Panel +from bpy.props import FloatProperty, CollectionProperty +from .archipack_object import ArchipackObject + + +def update_wall(self, context): + self.update(context) + + +class archipack_wall(ArchipackObject, PropertyGroup): + z = FloatProperty( + name='height', + min=0.1, max=10000, + default=2.7, precision=2, + description='height', update=update_wall, + ) + + def update(self, context): + # update height via bmesh to avoid loosing material ids + # this should be the rule for other simple objects + # as long as there is no topologic changes + o = context.active_object + if archipack_wall.datablock(o) != self: + return + bpy.ops.object.mode_set(mode='EDIT') + me = o.data + bm = bmesh.from_edit_mesh(me) + bm.verts.ensure_lookup_table() + bm.faces.ensure_lookup_table() + new_z = self.z + last_z = list(v.co.z for v in bm.verts) + max_z = max(last_z) + for v in bm.verts: + if v.co.z == max_z: + v.co.z = new_z + bmesh.update_edit_mesh(me, True) + bpy.ops.object.mode_set(mode='OBJECT') + + +class ARCHIPACK_PT_wall(Panel): + bl_idname = "ARCHIPACK_PT_wall" + bl_label = "Wall" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'ArchiPack' + + @classmethod + def poll(cls, context): + return archipack_wall.filter(context.active_object) + + def draw(self, context): + + prop = archipack_wall.datablock(context.active_object) + if prop is None: + return + layout = self.layout + layout.prop(prop, 'z') + + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_wall(Operator): + bl_idname = "archipack.wall" + bl_label = "Wall" + bl_description = "Add wall parameters to active object" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + z = FloatProperty( + name="z", + default=2.7 + ) + + @classmethod + def poll(cls, context): + return context.active_object is not None + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def execute(self, context): + if context.mode == "OBJECT": + o = context.active_object + if archipack_wall.filter(o): + return {'CANCELLED'} + params = o.data.archipack_wall.add() + params.z = self.z + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +def register(): + bpy.utils.register_class(archipack_wall) + Mesh.archipack_wall = CollectionProperty(type=archipack_wall) + bpy.utils.register_class(ARCHIPACK_PT_wall) + bpy.utils.register_class(ARCHIPACK_OT_wall) + + +def unregister(): + bpy.utils.unregister_class(archipack_wall) + del Mesh.archipack_wall + bpy.utils.unregister_class(ARCHIPACK_PT_wall) + bpy.utils.unregister_class(ARCHIPACK_OT_wall) diff --git a/archipack/archipack_wall2.py b/archipack/archipack_wall2.py new file mode 100644 index 000000000..4944f59fe --- /dev/null +++ b/archipack/archipack_wall2.py @@ -0,0 +1,2220 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +import bpy +# import time +from bpy.types import Operator, PropertyGroup, Mesh, Panel +from bpy.props import ( + FloatProperty, BoolProperty, IntProperty, StringProperty, + FloatVectorProperty, CollectionProperty, EnumProperty +) +from .bmesh_utils import BmeshEdit as bmed +from mathutils import Vector, Matrix +from mathutils.geometry import ( + interpolate_bezier + ) +from math import sin, cos, pi, atan2 +from .archipack_manipulator import ( + Manipulable, archipack_manipulator, + GlPolygon, GlPolyline, + GlLine, GlText, FeedbackPanel + ) +from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchpackDrawTool +from .archipack_2d import Line, Arc +from .archipack_snap import snap_point +from .archipack_keymaps import Keymaps + + +class Wall(): + def __init__(self, wall_z, z, t, flip): + self.z = z + self.wall_z = wall_z + self.t = t + self.flip = flip + self.z_step = len(z) + + def get_z(self, t): + t0 = self.t[0] + z0 = self.z[0] + for i in range(1, self.z_step): + t1 = self.t[i] + z1 = self.z[i] + if t <= t1: + return z0 + (t - t0) / (t1 - t0) * (z1 - z0) + t0, z0 = t1, z1 + return self.z[-1] + + def make_faces(self, i, f, faces): + if i < self.n_step: + # 1 3 5 7 + # 0 2 4 6 + if self.flip: + faces.append((f + 2, f, f + 1, f + 3)) + else: + faces.append((f, f + 2, f + 3, f + 1)) + + def p3d(self, verts, t): + x, y = self.lerp(t) + z = self.wall_z + self.get_z(t) + verts.append((x, y, 0)) + verts.append((x, y, z)) + + def make_wall(self, i, verts, faces): + t = self.t_step[i] + f = len(verts) + self.p3d(verts, t) + self.make_faces(i, f, faces) + + def straight_wall(self, a0, length, wall_z, z, t): + r = self.straight(length).rotate(a0) + return StraightWall(r.p, r.v, wall_z, z, t, self.flip) + + def curved_wall(self, a0, da, radius, wall_z, z, t): + n = self.normal(1).rotate(a0).scale(radius) + if da < 0: + n.v = -n.v + a0 = n.angle + c = n.p - n.v + return CurvedWall(c, radius, a0, da, wall_z, z, t, self.flip) + + +class StraightWall(Wall, Line): + def __init__(self, p, v, wall_z, z, t, flip): + Line.__init__(self, p, v) + Wall.__init__(self, wall_z, z, t, flip) + + def param_t(self, step_angle): + self.t_step = self.t + self.n_step = len(self.t) - 1 + + +class CurvedWall(Wall, Arc): + def __init__(self, c, radius, a0, da, wall_z, z, t, flip): + Arc.__init__(self, c, radius, a0, da) + Wall.__init__(self, wall_z, z, t, flip) + + def param_t(self, step_angle): + t_step, n_step = self.steps_by_angle(step_angle) + self.t_step = list(sorted([i * t_step for i in range(1, n_step)] + self.t)) + self.n_step = len(self.t_step) - 1 + + +class WallGenerator(): + def __init__(self, parts): + self.last_type = 'NONE' + self.segs = [] + self.parts = parts + self.faces_type = 'NONE' + self.closed = False + + def add_part(self, part, wall_z, flip): + + # TODO: + # refactor this part (height manipulators) + manip_index = [] + if len(self.segs) < 1: + s = None + z = [part.z[0]] + manip_index.append(0) + else: + s = self.segs[-1] + z = [s.z[-1]] + + t_cur = 0 + z_last = part.n_splits - 1 + t = [0] + + for i in range(part.n_splits): + t_try = t[-1] + part.t[i] + if t_try == t_cur: + continue + if t_try <= 1: + t_cur = t_try + t.append(t_cur) + z.append(part.z[i]) + manip_index.append(i) + else: + z_last = i + break + + if t_cur < 1: + t.append(1) + manip_index.append(z_last) + z.append(part.z[z_last]) + + # start a new wall + if s is None: + if part.type == 'S_WALL': + p = Vector((0, 0)) + v = part.length * Vector((cos(part.a0), sin(part.a0))) + s = StraightWall(p, v, wall_z, z, t, flip) + elif part.type == 'C_WALL': + c = -part.radius * Vector((cos(part.a0), sin(part.a0))) + s = CurvedWall(c, part.radius, part.a0, part.da, wall_z, z, t, flip) + else: + if part.type == 'S_WALL': + s = s.straight_wall(part.a0, part.length, wall_z, z, t) + elif part.type == 'C_WALL': + s = s.curved_wall(part.a0, part.da, part.radius, wall_z, z, t) + + self.segs.append(s) + self.last_type = part.type + + return manip_index + + def close(self, closed): + # Make last segment implicit closing one + if closed: + part = self.parts[-1] + w = self.segs[-1] + dp = self.segs[0].p0 - self.segs[-1].p0 + if "C_" in part.type: + dw = (w.p1 - w.p0) + w.r = part.radius / dw.length * dp.length + # angle pt - p0 - angle p0 p1 + da = atan2(dp.y, dp.x) - atan2(dw.y, dw.x) + a0 = w.a0 + da + if a0 > pi: + a0 -= 2 * pi + if a0 < -pi: + a0 += 2 * pi + w.a0 = a0 + else: + w.v = dp + + def make_wall(self, step_angle, flip, closed, verts, faces): + + # swap manipulators so they always face outside + side = 1 + if flip: + side = -1 + + # Make last segment implicit closing one + + nb_segs = len(self.segs) - 1 + if closed: + nb_segs += 1 + + for i, wall in enumerate(self.segs): + + manipulators = self.parts[i].manipulators + + p0 = wall.p0.to_3d() + p1 = wall.p1.to_3d() + + # angle from last to current segment + if i > 0: + + if i < len(self.segs) - 1: + manipulators[0].type_key = 'ANGLE' + else: + manipulators[0].type_key = 'DUMB_ANGLE' + + v0 = self.segs[i - 1].straight(-side, 1).v.to_3d() + v1 = wall.straight(side, 0).v.to_3d() + manipulators[0].set_pts([p0, v0, v1]) + + if type(wall).__name__ == "StraightWall": + # segment length + manipulators[1].type_key = 'SIZE' + manipulators[1].prop1_name = "length" + manipulators[1].set_pts([p0, p1, (side, 0, 0)]) + else: + # segment radius + angle + # scale to fix overlap with drag + v0 = side * (wall.p0 - wall.c).to_3d() + v1 = side * (wall.p1 - wall.c).to_3d() + scale = 1.0 + (0.5 / v0.length) + manipulators[1].type_key = 'ARC_ANGLE_RADIUS' + manipulators[1].prop1_name = "da" + manipulators[1].prop2_name = "radius" + manipulators[1].set_pts([wall.c.to_3d(), scale * v0, scale * v1]) + + # snap manipulator, dont change index ! + manipulators[2].set_pts([p0, p1, (1, 0, 0)]) + + # dumb, segment index + z = Vector((0, 0, 0.75 * wall.wall_z)) + manipulators[3].set_pts([p0 + z, p1 + z, (1, 0, 0)]) + + wall.param_t(step_angle) + if i < nb_segs: + for j in range(wall.n_step + 1): + wall.make_wall(j, verts, faces) + else: + # last segment + for j in range(wall.n_step): + continue + # print("%s" % (wall.n_step)) + # wall.make_wall(j, verts, faces) + + def rotate(self, idx_from, a): + """ + apply rotation to all following segs + """ + self.segs[idx_from].rotate(a) + ca = cos(a) + sa = sin(a) + rM = Matrix([ + [ca, -sa], + [sa, ca] + ]) + # rotation center + p0 = self.segs[idx_from].p0 + for i in range(idx_from + 1, len(self.segs)): + seg = self.segs[i] + seg.rotate(a) + dp = rM * (seg.p0 - p0) + seg.translate(dp) + + def translate(self, idx_from, dp): + """ + apply translation to all following segs + """ + self.segs[idx_from].p1 += dp + for i in range(idx_from + 1, len(self.segs)): + self.segs[i].translate(dp) + + def draw(self, context): + for seg in self.segs: + seg.draw(context, render=False) + + def debug(self, verts): + for wall in self.segs: + for i in range(33): + x, y = wall.lerp(i / 32) + verts.append((x, y, 0)) + + +def update(self, context): + self.update(context) + + +def update_childs(self, context): + self.update(context, update_childs=True, manipulable_refresh=True) + + +def update_manipulators(self, context): + self.update(context, manipulable_refresh=True) + + +def update_t_part(self, context): + """ + Make this wall a T child of parent wall + orient child so y points inside wall and x follow wall segment + set child a0 according + """ + o = self.find_in_selection(context) + if o is not None: + + # w is parent wall + w = context.scene.objects.get(self.t_part) + wd = archipack_wall2.datablock(w) + + if wd is not None: + og = self.get_generator() + self.setup_childs(o, og) + + bpy.ops.object.select_all(action="DESELECT") + + # 5 cases here: + # 1 No parents at all + # 2 o has parent + # 3 w has parent + # 4 o and w share same parent allready + # 5 o and w dosent share parent + link_to_parent = False + + # when both walls do have a reference point, we may delete one of them + to_delete = None + + # select childs and make parent reference point active + if w.parent is None: + # Either link to o.parent or create new parent + link_to_parent = True + if o.parent is None: + # create a reference point and make it active + x, y, z = w.bound_box[0] + context.scene.cursor_location = w.matrix_world * Vector((x, y, z)) + # fix issue #9 + context.scene.objects.active = o + bpy.ops.archipack.reference_point() + o.select = True + else: + context.scene.objects.active = o.parent + w.select = True + else: + # w has parent + if o.parent is not w.parent: + link_to_parent = True + context.scene.objects.active = w.parent + o.select = True + if o.parent is not None: + # store o.parent to delete it + to_delete = o.parent + for c in o.parent.children: + if c is not o: + c.hide_select = False + c.select = True + + parent = context.active_object + + dmax = 2 * wd.width + + wg = wd.get_generator() + + otM = o.matrix_world + orM = Matrix([ + otM[0].to_2d(), + otM[1].to_2d() + ]) + + wtM = w.matrix_world + wrM = Matrix([ + wtM[0].to_2d(), + wtM[1].to_2d() + ]) + + # dir in absolute world coordsys + dir = orM * og.segs[0].straight(1, 0).v + + # pt in w coordsys + pos = otM.translation + pt = (wtM.inverted() * pos).to_2d() + + for wall_idx, wall in enumerate(wg.segs): + res, dist, t = wall.point_sur_segment(pt) + # outside is on the right side of the wall + # p1 + # |-- x + # p0 + + # NOTE: + # rotation here is wrong when w has not parent while o has parent + + if res and t > 0 and t < 1 and abs(dist) < dmax: + x = wrM * wall.straight(1, t).v + y = wrM * wall.normal(t).v.normalized() + self.parts[0].a0 = dir.angle_signed(x) + o.matrix_world = Matrix([ + [x.x, -y.x, 0, pos.x], + [x.y, -y.y, 0, pos.y], + [0, 0, 1, pos.z], + [0, 0, 0, 1] + ]) + break + + if link_to_parent and bpy.ops.archipack.parent_to_reference.poll(): + bpy.ops.archipack.parent_to_reference('INVOKE_DEFAULT') + + # update generator to take new rotation in account + # use this to relocate windows on wall after reparenting + g = self.get_generator() + self.relocate_childs(context, o, g) + + # hide holes from select + for c in parent.children: + if "archipack_hybridhole" in c: + c.hide_select = True + + # delete unneeded reference point + if to_delete is not None: + bpy.ops.object.select_all(action="DESELECT") + to_delete.select = True + context.scene.objects.active = to_delete + if bpy.ops.object.delete.poll(): + bpy.ops.object.delete(use_global=False) + + elif self.t_part != "": + self.t_part = "" + + self.restore_context(context) + + +def set_splits(self, value): + if self.n_splits != value: + self.auto_update = False + self._set_t(value) + self.auto_update = True + self.n_splits = value + return None + + +def get_splits(self): + return self.n_splits + + +def update_type(self, context): + + d = self.find_datablock_in_selection(context) + + if d is not None and d.auto_update: + + d.auto_update = False + idx = 0 + for i, part in enumerate(d.parts): + if part == self: + idx = i + break + a0 = 0 + if idx > 0: + g = d.get_generator() + w0 = g.segs[idx - 1] + a0 = w0.straight(1).angle + if "C_" in self.type: + w = w0.straight_wall(self.a0, self.length, d.z, self.z, self.t) + else: + w = w0.curved_wall(self.a0, self.da, self.radius, d.z, self.z, self.t) + else: + g = WallGenerator(None) + g.add_part(self, d.z, d.flip) + w = g.segs[0] + # w0 - w - w1 + dp = w.p1 - w.p0 + if "C_" in self.type: + self.radius = 0.5 * dp.length + self.da = pi + a0 = atan2(dp.y, dp.x) - pi / 2 - a0 + else: + self.length = dp.length + a0 = atan2(dp.y, dp.x) - a0 + + if a0 > pi: + a0 -= 2 * pi + if a0 < -pi: + a0 += 2 * pi + self.a0 = a0 + + if idx + 1 < d.n_parts: + # adjust rotation of next part + part1 = d.parts[idx + 1] + if "C_" in self.type: + a0 = part1.a0 - pi / 2 + else: + a0 = part1.a0 + w.straight(1).angle - atan2(dp.y, dp.x) + + if a0 > pi: + a0 -= 2 * pi + if a0 < -pi: + a0 += 2 * pi + part1.a0 = a0 + + d.auto_update = True + + +class archipack_wall2_part(PropertyGroup): + type = EnumProperty( + items=( + ('S_WALL', 'Straight', '', 0), + ('C_WALL', 'Curved', '', 1) + ), + default='S_WALL', + update=update_type + ) + length = FloatProperty( + name="length", + min=0.01, + default=2.0, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + radius = FloatProperty( + name="radius", + min=0.5, + default=0.7, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + a0 = FloatProperty( + name="start angle", + min=-pi, + max=pi, + default=pi / 2, + subtype='ANGLE', unit='ROTATION', + update=update + ) + da = FloatProperty( + name="angle", + min=-pi, + max=pi, + default=pi / 2, + subtype='ANGLE', unit='ROTATION', + update=update + ) + z = FloatVectorProperty( + name="height", + min=0, + default=[ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0 + ], + size=31, + update=update + ) + t = FloatVectorProperty( + name="position", + min=0, + max=1, + default=[ + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1 + ], + size=31, + update=update + ) + splits = IntProperty( + name="splits", + default=1, + min=1, + max=31, + get=get_splits, set=set_splits + ) + n_splits = IntProperty( + name="splits", + default=1, + min=1, + max=31, + update=update + ) + auto_update = BoolProperty(default=True) + manipulators = CollectionProperty(type=archipack_manipulator) + # ui related + expand = BoolProperty(default=False) + + def _set_t(self, splits): + t = 1 / splits + for i in range(splits): + self.t[i] = t + + def find_datablock_in_selection(self, context): + """ + find witch selected object this instance belongs to + provide support for "copy to selected" + """ + selected = [o for o in context.selected_objects] + for o in selected: + props = archipack_wall2.datablock(o) + if props: + for part in props.parts: + if part == self: + return props + return None + + def update(self, context, manipulable_refresh=False): + if not self.auto_update: + return + props = self.find_datablock_in_selection(context) + if props is not None: + props.update(context, manipulable_refresh) + + def draw(self, layout, context, index): + + row = layout.row(align=True) + if self.expand: + row.prop(self, 'expand', icon="TRIA_DOWN", icon_only=True, text="Part " + str(index + 1), emboss=False) + else: + row.prop(self, 'expand', icon="TRIA_RIGHT", icon_only=True, text="Part " + str(index + 1), emboss=False) + + row.prop(self, "type", text="") + + if self.expand: + row = layout.row(align=True) + row.operator("archipack.wall2_insert", text="Split").index = index + row.operator("archipack.wall2_remove", text="Remove").index = index + if self.type == 'C_WALL': + row = layout.row() + row.prop(self, "radius") + row = layout.row() + row.prop(self, "da") + else: + row = layout.row() + row.prop(self, "length") + row = layout.row() + row.prop(self, "a0") + row = layout.row() + row.prop(self, "splits") + for split in range(self.n_splits): + row = layout.row() + row.prop(self, "z", text="alt", index=split) + row.prop(self, "t", text="pos", index=split) + + +class archipack_wall2_child(PropertyGroup): + # Size Loc + # Delta Loc + manipulators = CollectionProperty(type=archipack_manipulator) + child_name = StringProperty() + wall_idx = IntProperty() + pos = FloatVectorProperty(subtype='XYZ') + flip = BoolProperty(default=False) + + def get_child(self, context): + d = None + child = context.scene.objects.get(self.child_name) + if child is not None and child.data is not None: + cd = child.data + if 'archipack_window' in cd: + d = cd.archipack_window[0] + elif 'archipack_door' in cd: + d = cd.archipack_door[0] + return child, d + + +class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): + parts = CollectionProperty(type=archipack_wall2_part) + n_parts = IntProperty( + name="parts", + min=1, + max=1024, + default=1, update=update_manipulators + ) + step_angle = FloatProperty( + description="Curved parts segmentation", + name="step angle", + min=1 / 180 * pi, + max=pi, + default=6 / 180 * pi, + subtype='ANGLE', unit='ROTATION', + update=update + ) + width = FloatProperty( + name="width", + min=0.01, + default=0.2, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + z = FloatProperty( + name='height', + min=0.1, + default=2.7, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='height', update=update, + ) + x_offset = FloatProperty( + name="x offset", + min=-1, max=1, + default=-1, precision=2, step=1, + update=update + ) + radius = FloatProperty( + name="radius", + min=0.5, + default=0.7, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + da = FloatProperty( + name="angle", + min=-pi, + max=pi, + default=pi / 2, + subtype='ANGLE', unit='ROTATION', + update=update + ) + flip = BoolProperty(default=False, update=update_childs) + closed = BoolProperty( + default=False, + name="Close", + update=update_manipulators + ) + auto_update = BoolProperty( + options={'SKIP_SAVE'}, + default=True, + update=update_manipulators + ) + realtime = BoolProperty( + options={'SKIP_SAVE'}, + default=True, + name="RealTime", + description="Relocate childs in realtime" + ) + # dumb manipulators to show sizes between childs + childs_manipulators = CollectionProperty(type=archipack_manipulator) + # store to manipulate windows and doors + childs = CollectionProperty(type=archipack_wall2_child) + t_part = StringProperty( + name="Parent wall", + description="This part will follow parent when set", + default="", + update=update_t_part + ) + + def insert_part(self, context, o, where): + self.manipulable_disable(context) + self.auto_update = False + # the part we do split + part_0 = self.parts[where] + part_0.length /= 2 + part_0.da /= 2 + self.parts.add() + part_1 = self.parts[len(self.parts) - 1] + part_1.type = part_0.type + part_1.length = part_0.length + part_1.da = part_0.da + part_1.a0 = 0 + # move after current one + self.parts.move(len(self.parts) - 1, where + 1) + self.n_parts += 1 + # re-eval childs location + g = self.get_generator() + self.setup_childs(o, g) + + self.setup_manipulators() + self.auto_update = True + + def add_part(self, context, length): + self.manipulable_disable(context) + self.auto_update = False + p = self.parts.add() + p.length = length + self.parts.move(len(self.parts) - 1, self.n_parts) + self.n_parts += 1 + self.setup_manipulators() + self.auto_update = True + return self.parts[self.n_parts - 1] + + def remove_part(self, context, o, where): + self.manipulable_disable(context) + self.auto_update = False + # preserve shape + # using generator + if where > 0: + g = self.get_generator() + w = g.segs[where - 1] + w.p1 = g.segs[where].p1 + + if where + 1 < self.n_parts: + self.parts[where + 1].a0 = g.segs[where + 1].delta_angle(w) + + part = self.parts[where - 1] + + if "C_" in part.type: + part.radius = w.r + else: + part.length = w.length + + if where > 1: + part.a0 = w.delta_angle(g.segs[where - 2]) + else: + part.a0 = w.straight(1, 0).angle + + self.parts.remove(where) + self.n_parts -= 1 + + # re-eval child location + g = self.get_generator() + self.setup_childs(o, g) + + # fix snap manipulators index + self.setup_manipulators() + self.auto_update = True + + def get_generator(self): + # print("get_generator") + g = WallGenerator(self.parts) + for part in self.parts: + g.add_part(part, self.z, self.flip) + g.close(self.closed) + return g + + def update_parts(self, o, update_childs=False): + # print("update_parts") + # remove rows + # NOTE: + # n_parts+1 + # as last one is end point of last segment or closing one + row_change = False + for i in range(len(self.parts), self.n_parts + 1, -1): + row_change = True + self.parts.remove(i - 1) + + # add rows + for i in range(len(self.parts), self.n_parts + 1): + row_change = True + self.parts.add() + + self.setup_manipulators() + + g = self.get_generator() + + if o is not None and (row_change or update_childs): + self.setup_childs(o, g) + + return g + + def setup_manipulators(self): + + if len(self.manipulators) == 0: + # make manipulators selectable + s = self.manipulators.add() + s.prop1_name = "width" + s = self.manipulators.add() + s.prop1_name = "n_parts" + s.type_key = 'COUNTER' + s = self.manipulators.add() + s.prop1_name = "z" + s.normal = (0, 1, 0) + + if self.t_part != "" and len(self.manipulators) < 4: + s = self.manipulators.add() + s.prop1_name = "x" + s.type_key = 'DELTA_LOC' + + for i in range(self.n_parts + 1): + p = self.parts[i] + n_manips = len(p.manipulators) + if n_manips < 1: + s = p.manipulators.add() + s.type_key = "ANGLE" + s.prop1_name = "a0" + if n_manips < 2: + s = p.manipulators.add() + s.type_key = "SIZE" + s.prop1_name = "length" + if n_manips < 3: + s = p.manipulators.add() + s.type_key = 'WALL_SNAP' + s.prop1_name = str(i) + s.prop2_name = 'z' + if n_manips < 4: + s = p.manipulators.add() + s.type_key = 'DUMB_STRING' + s.prop1_name = str(i + 1) + p.manipulators[2].prop1_name = str(i) + p.manipulators[3].prop1_name = str(i + 1) + + def interpolate_bezier(self, pts, wM, p0, p1, resolution): + if resolution == 0: + pts.append(wM * p0.co.to_3d()) + else: + v = (p1.co - p0.co).normalized() + d1 = (p0.handle_right - p0.co).normalized() + d2 = (p1.co - p1.handle_left).normalized() + if d1 == v and d2 == v: + pts.append(wM * p0.co.to_3d()) + else: + seg = interpolate_bezier(wM * p0.co, + wM * p0.handle_right, + wM * p1.handle_left, + wM * p1.co, + resolution + 1) + for i in range(resolution): + pts.append(seg[i].to_3d()) + + def is_cw(self, pts): + p0 = pts[0] + d = 0 + for p in pts[1:]: + d += (p.x * p0.y - p.y * p0.x) + p0 = p + return d > 0 + + def from_spline(self, wM, resolution, spline): + pts = [] + if spline.type == 'POLY': + pts = [wM * p.co.to_3d() for p in spline.points] + if spline.use_cyclic_u: + pts.append(pts[0]) + elif spline.type == 'BEZIER': + points = spline.bezier_points + for i in range(1, len(points)): + p0 = points[i - 1] + p1 = points[i] + self.interpolate_bezier(pts, wM, p0, p1, resolution) + if spline.use_cyclic_u: + p0 = points[-1] + p1 = points[0] + self.interpolate_bezier(pts, wM, p0, p1, resolution) + pts.append(pts[0]) + else: + pts.append(wM * points[-1].co) + + if self.is_cw(pts): + pts = list(reversed(pts)) + + self.auto_update = False + self.from_points(pts, spline.use_cyclic_u) + self.auto_update = True + + def from_points(self, pts, closed): + + self.n_parts = len(pts) - 1 + + if closed: + self.n_parts -= 1 + + self.update_parts(None) + + p0 = pts.pop(0) + a0 = 0 + for i, p1 in enumerate(pts): + dp = p1 - p0 + da = atan2(dp.y, dp.x) - a0 + if da > pi: + da -= 2 * pi + if da < -pi: + da += 2 * pi + if i >= len(self.parts): + print("Too many pts for parts") + break + p = self.parts[i] + p.length = dp.to_2d().length + p.dz = dp.z + p.a0 = da + a0 += da + p0 = p1 + + self.closed = closed + + def reverse(self, context, o): + + g = self.get_generator() + pts = [seg.p0.to_3d() for seg in g.segs] + + if self.closed: + pts.append(pts[0]) + + pts = list(reversed(pts)) + self.auto_update = False + + # location wont change for closed walls + if not self.closed: + dp = pts[0] - pts[-1] + # pre-translate as dp is in local coordsys + o.matrix_world = o.matrix_world * Matrix([ + [1, 0, 0, dp.x], + [0, 1, 0, dp.y], + [0, 0, 1, 0], + [0, 0, 0, 1], + ]) + + self.from_points(pts, self.closed) + g = self.get_generator() + + self.setup_childs(o, g) + self.auto_update = True + + # flip does trigger relocate and keep childs orientation + self.flip = not self.flip + + def update(self, context, manipulable_refresh=False, update_childs=False): + + o = self.find_in_selection(context, self.auto_update) + + if o is None: + return + + if manipulable_refresh: + # prevent crash by removing all manipulators refs to datablock before changes + self.manipulable_disable(context) + + verts = [] + faces = [] + + g = self.update_parts(o, update_childs) + # print("make_wall") + g.make_wall(self.step_angle, self.flip, self.closed, verts, faces) + + if self.closed: + f = len(verts) + if self.flip: + faces.append((0, f - 2, f - 1, 1)) + else: + faces.append((f - 2, 0, 1, f - 1)) + + # print("buildmesh") + bmed.buildmesh(context, o, verts, faces, matids=None, uvs=None, weld=True) + + side = 1 + if self.flip: + side = -1 + # Width + offset = side * (0.5 * self.x_offset) * self.width + self.manipulators[0].set_pts([ + g.segs[0].sized_normal(0, offset + 0.5 * side * self.width).v.to_3d(), + g.segs[0].sized_normal(0, offset - 0.5 * side * self.width).v.to_3d(), + (-side, 0, 0) + ]) + + # Parts COUNTER + self.manipulators[1].set_pts([g.segs[-2].lerp(1.1).to_3d(), + g.segs[-2].lerp(1.1 + 0.5 / g.segs[-2].length).to_3d(), + (-side, 0, 0) + ]) + + # Height + self.manipulators[2].set_pts([ + (0, 0, 0), + (0, 0, self.z), + (-1, 0, 0) + ], normal=g.segs[0].straight(side, 0).v.to_3d()) + + if self.t_part != "": + t = 0.3 / g.segs[0].length + self.manipulators[3].set_pts([ + g.segs[0].sized_normal(t, 0.1).p1.to_3d(), + g.segs[0].sized_normal(t, -0.1).p1.to_3d(), + (1, 0, 0) + ]) + + if self.realtime: + # update child location and size + self.relocate_childs(context, o, g) + # store gl points + self.update_childs(context, o, g) + else: + bpy.ops.archipack.wall2_throttle_update(name=o.name) + + modif = o.modifiers.get('Wall') + if modif is None: + modif = o.modifiers.new('Wall', 'SOLIDIFY') + modif.use_quality_normals = True + modif.use_even_offset = True + modif.material_offset_rim = 2 + modif.material_offset = 1 + + modif.thickness = self.width + modif.offset = self.x_offset + + if manipulable_refresh: + # print("manipulable_refresh=True") + self.manipulable_refresh = True + + self.restore_context(context) + + # manipulable children objects like windows and doors + def child_partition(self, array, begin, end): + pivot = begin + for i in range(begin + 1, end + 1): + # wall idx + if array[i][1] < array[begin][1]: + pivot += 1 + array[i], array[pivot] = array[pivot], array[i] + # param t on the wall + elif array[i][1] == array[begin][1] and array[i][4] <= array[begin][4]: + pivot += 1 + array[i], array[pivot] = array[pivot], array[i] + array[pivot], array[begin] = array[begin], array[pivot] + return pivot + + def sort_child(self, array, begin=0, end=None): + # print("sort_child") + if end is None: + end = len(array) - 1 + + def _quicksort(array, begin, end): + if begin >= end: + return + pivot = self.child_partition(array, begin, end) + _quicksort(array, begin, pivot - 1) + _quicksort(array, pivot + 1, end) + return _quicksort(array, begin, end) + + def add_child(self, name, wall_idx, pos, flip): + # print("add_child %s %s" % (name, wall_idx)) + c = self.childs.add() + c.child_name = name + c.wall_idx = wall_idx + c.pos = pos + c.flip = flip + m = c.manipulators.add() + m.type_key = 'DELTA_LOC' + m.prop1_name = "x" + m = c.manipulators.add() + m.type_key = 'SNAP_SIZE_LOC' + m.prop1_name = "x" + m.prop2_name = "x" + + def setup_childs(self, o, g): + """ + Store childs + create manipulators + call after a boolean oop + """ + # tim = time.time() + self.childs.clear() + self.childs_manipulators.clear() + if o.parent is None: + return + wall_with_childs = [0 for i in range(self.n_parts + 1)] + relocate = [] + dmax = 2 * self.width + + wtM = o.matrix_world + wrM = Matrix([ + wtM[0].to_2d(), + wtM[1].to_2d() + ]) + witM = wtM.inverted() + + for child in o.parent.children: + # filter allowed childs + cd = child.data + wd = archipack_wall2.datablock(child) + if (child != o and cd is not None and ( + 'archipack_window' in cd or + 'archipack_door' in cd or ( + wd is not None and + o.name in wd.t_part + ) + )): + + # setup on T linked walls + if wd is not None: + wg = wd.get_generator() + wd.setup_childs(child, wg) + + ctM = child.matrix_world + crM = Matrix([ + ctM[0].to_2d(), + ctM[1].to_2d() + ]) + + # pt in w coordsys + pos = ctM.translation + pt = (witM * pos).to_2d() + + for wall_idx, wall in enumerate(g.segs): + # may be optimized with a bound check + res, dist, t = wall.point_sur_segment(pt) + # outside is on the right side of the wall + # p1 + # |-- x + # p0 + if res and t > 0 and t < 1 and abs(dist) < dmax: + # dir in world coordsys + dir = wrM * wall.sized_normal(t, 1).v + wall_with_childs[wall_idx] = 1 + m = self.childs_manipulators.add() + m.type_key = 'DUMB_SIZE' + # always make window points outside + if "archipack_window" in cd: + flip = self.flip + else: + dir_y = crM * Vector((0, -1)) + # let door orient where user want + flip = (dir_y - dir).length > 0.5 + # store z in wall space + relocate.append(( + child.name, + wall_idx, + (t * wall.length, dist, (witM * pos).z), + flip, + t)) + break + + self.sort_child(relocate) + for child in relocate: + name, wall_idx, pos, flip, t = child + self.add_child(name, wall_idx, pos, flip) + + # add a dumb size from last child to end of wall segment + for i in range(sum(wall_with_childs)): + m = self.childs_manipulators.add() + m.type_key = 'DUMB_SIZE' + # print("setup_childs:%1.4f" % (time.time()-tim)) + + def relocate_childs(self, context, o, g): + """ + Move and resize childs after wall edition + """ + # print("relocate_childs") + # tim = time.time() + w = -self.x_offset * self.width + if self.flip: + w = -w + tM = o.matrix_world + for child in self.childs: + c, d = child.get_child(context) + if c is None: + continue + t = child.pos.x / g.segs[child.wall_idx].length + n = g.segs[child.wall_idx].sized_normal(t, 1) + rx, ry = -n.v + rx, ry = ry, -rx + if child.flip: + rx, ry = -rx, -ry + + if d is not None: + # print("change flip:%s width:%s" % (d.flip != child.flip, d.y != self.width)) + if d.y != self.width or d.flip != child.flip: + c.select = True + d.auto_update = False + d.flip = child.flip + d.y = self.width + d.auto_update = True + c.select = False + x, y = n.p - (0.5 * w * n.v) + else: + x, y = n.p - (child.pos.y * n.v) + + context.scene.objects.active = o + # preTranslate + c.matrix_world = tM * Matrix([ + [rx, -ry, 0, x], + [ry, rx, 0, y], + [0, 0, 1, child.pos.z], + [0, 0, 0, 1] + ]) + + # Update T linked wall's childs + if archipack_wall2.filter(c): + d = archipack_wall2.datablock(c) + cg = d.get_generator() + d.relocate_childs(context, c, cg) + + # print("relocate_childs:%1.4f" % (time.time()-tim)) + + def update_childs(self, context, o, g): + """ + setup gl points for childs + """ + # print("update_childs") + + if o.parent is None: + return + + # swap manipulators so they always face outside + manip_side = 1 + if self.flip: + manip_side = -1 + + itM = o.matrix_world.inverted() + m_idx = 0 + for wall_idx, wall in enumerate(g.segs): + p0 = wall.lerp(0) + wall_has_childs = False + for child in self.childs: + if child.wall_idx == wall_idx: + c, d = child.get_child(context) + if d is not None: + # child is either a window or a door + wall_has_childs = True + dt = 0.5 * d.x / wall.length + pt = (itM * c.matrix_world.translation).to_2d() + res, y, t = wall.point_sur_segment(pt) + child.pos = (wall.length * t, y, child.pos.z) + p1 = wall.lerp(t - dt) + # dumb size between childs + self.childs_manipulators[m_idx].set_pts([ + (p0.x, p0.y, 0), + (p1.x, p1.y, 0), + (manip_side * 0.5, 0, 0)]) + m_idx += 1 + x, y = 0.5 * d.x, -self.x_offset * 0.5 * d.y + + if child.flip: + side = -manip_side + else: + side = manip_side + + # delta loc + child.manipulators[0].set_pts([(-x, side * -y, 0), (x, side * -y, 0), (side, 0, 0)]) + # loc size + child.manipulators[1].set_pts([ + (-x, side * -y, 0), + (x, side * -y, 0), + (0.5 * side, 0, 0)]) + p0 = wall.lerp(t + dt) + p1 = wall.lerp(1) + if wall_has_childs: + # dub size after all childs + self.childs_manipulators[m_idx].set_pts([ + (p0.x, p0.y, 0), + (p1.x, p1.y, 0), + (manip_side * 0.5, 0, 0)]) + m_idx += 1 + + def manipulate_childs(self, context): + """ + setup child manipulators + """ + # print("manipulate_childs") + n_parts = self.n_parts + if self.closed: + n_parts += 1 + + for wall_idx in range(n_parts): + for child in self.childs: + if child.wall_idx == wall_idx: + c, d = child.get_child(context) + if d is not None: + # delta loc + self.manip_stack.append(child.manipulators[0].setup(context, c, d, self.manipulate_callback)) + # loc size + self.manip_stack.append(child.manipulators[1].setup(context, c, d, self.manipulate_callback)) + + def manipulate_callback(self, context, o=None, manipulator=None): + found = False + if o.parent is not None: + for c in o.parent.children: + if (archipack_wall2.datablock(c) == self): + context.scene.objects.active = c + found = True + break + if found: + self.manipulable_manipulate(context, manipulator=manipulator) + + def manipulable_manipulate(self, context, event=None, manipulator=None): + type_name = type(manipulator).__name__ + # print("manipulable_manipulate %s" % (type_name)) + if type_name in [ + 'DeltaLocationManipulator', + 'SizeLocationManipulator', + 'SnapSizeLocationManipulator' + ]: + # update manipulators pos of childs + o = context.active_object + if o.parent is None: + return + g = self.get_generator() + itM = o.matrix_world.inverted() * o.parent.matrix_world + for child in self.childs: + c, d = child.get_child(context) + if d is not None: + wall = g.segs[child.wall_idx] + pt = (itM * c.location).to_2d() + res, d, t = wall.point_sur_segment(pt) + child.pos = (t * wall.length, d, child.pos.z) + # update childs manipulators + self.update_childs(context, o, g) + + def manipulable_move_t_part(self, context, o=None, manipulator=None): + type_name = type(manipulator).__name__ + # print("manipulable_manipulate %s" % (type_name)) + if type_name in [ + 'DeltaLocationManipulator' + ]: + # update manipulators pos of childs + if archipack_wall2.datablock(o) != self: + return + g = self.get_generator() + # update childs + self.relocate_childs(context, o, g) + + def manipulable_release(self, context): + """ + Override with action to do on mouse release + eg: big update + """ + return + + def manipulable_setup(self, context): + # print("manipulable_setup") + self.manipulable_disable(context) + o = context.active_object + + # setup childs manipulators + self.manipulate_childs(context) + n_parts = self.n_parts + if self.closed: + n_parts += 1 + + # update manipulators on version change + self.setup_manipulators() + + for i, part in enumerate(self.parts): + + if i < n_parts: + if i > 0: + # start angle + self.manip_stack.append(part.manipulators[0].setup(context, o, part)) + + # length / radius + angle + self.manip_stack.append(part.manipulators[1].setup(context, o, part)) + # segment index + self.manip_stack.append(part.manipulators[3].setup(context, o, self)) + + # snap point + self.manip_stack.append(part.manipulators[2].setup(context, o, self)) + + # height as per segment will be here when done + + # width + counter + for m in self.manipulators: + self.manip_stack.append(m.setup(context, o, self, self.manipulable_move_t_part)) + + # dumb between childs + for m in self.childs_manipulators: + self.manip_stack.append(m.setup(context, o, self)) + + def manipulable_exit(self, context): + """ + Override with action to do when modal exit + """ + return + + def manipulable_invoke(self, context): + """ + call this in operator invoke() + """ + # print("manipulable_invoke") + if self.manipulate_mode: + self.manipulable_disable(context) + return False + + # self.manip_stack = [] + o = context.active_object + g = self.get_generator() + # setup childs manipulators + self.setup_childs(o, g) + # store gl points + self.update_childs(context, o, g) + # dont do anything .. + # self.manipulable_release(context) + # self.manipulate_mode = True + self.manipulable_setup(context) + self.manipulate_mode = True + + self._manipulable_invoke(context) + + return True + + +# Update throttle (smell hack here) +# use 2 globals to store a timer and state of update_action +# NO MORE USING THIS PART, kept as it as it may be usefull in some cases +update_timer = None +update_timer_updating = False + + +class ARCHIPACK_OT_wall2_throttle_update(Operator): + bl_idname = "archipack.wall2_throttle_update" + bl_label = "Update childs with a delay" + + name = StringProperty() + + def modal(self, context, event): + global update_timer_updating + if event.type == 'TIMER' and not update_timer_updating: + update_timer_updating = True + o = context.scene.objects.get(self.name) + # print("delay update of %s" % (self.name)) + if o is not None: + o.select = True + context.scene.objects.active = o + d = o.data.archipack_wall2[0] + g = d.get_generator() + # update child location and size + d.relocate_childs(context, o, g) + # store gl points + d.update_childs(context, o, g) + return self.cancel(context) + return {'PASS_THROUGH'} + + def execute(self, context): + global update_timer + global update_timer_updating + if update_timer is not None: + if update_timer_updating: + return {'CANCELLED'} + # reset update_timer so it only occurs once 0.1s after last action + context.window_manager.event_timer_remove(update_timer) + update_timer = context.window_manager.event_timer_add(0.1, context.window) + return {'CANCELLED'} + update_timer_updating = False + context.window_manager.modal_handler_add(self) + update_timer = context.window_manager.event_timer_add(0.1, context.window) + return {'RUNNING_MODAL'} + + def cancel(self, context): + global update_timer + context.window_manager.event_timer_remove(update_timer) + update_timer = None + return {'CANCELLED'} + + +class ARCHIPACK_PT_wall2(Panel): + bl_idname = "ARCHIPACK_PT_wall2" + bl_label = "Wall" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'ArchiPack' + + def draw(self, context): + prop = archipack_wall2.datablock(context.object) + if prop is None: + return + layout = self.layout + row = layout.row(align=True) + row.operator("archipack.wall2_manipulate", icon='HAND') + # row = layout.row(align=True) + # row.prop(prop, 'realtime') + box = layout.box() + box.prop(prop, 'n_parts') + box.prop(prop, 'step_angle') + box.prop(prop, 'width') + box.prop(prop, 'z') + box.prop(prop, 'flip') + box.prop(prop, 'x_offset') + row = layout.row() + row.prop(prop, "closed") + row = layout.row() + row.prop_search(prop, "t_part", context.scene, "objects", text="T link", icon='OBJECT_DATAMODE') + row = layout.row() + row.operator("archipack.wall2_reverse", icon='FILE_REFRESH') + n_parts = prop.n_parts + if prop.closed: + n_parts += 1 + for i, part in enumerate(prop.parts): + if i < n_parts: + box = layout.box() + part.draw(box, context, i) + + @classmethod + def poll(cls, context): + return archipack_wall2.filter(context.active_object) + + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_wall2(ArchipackCreateTool, Operator): + bl_idname = "archipack.wall2" + bl_label = "Wall" + bl_description = "Create a Wall" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + def create(self, context): + m = bpy.data.meshes.new("Wall") + o = bpy.data.objects.new("Wall", m) + d = m.archipack_wall2.add() + d.manipulable_selectable = True + context.scene.objects.link(o) + o.select = True + # around 12 degree + m.auto_smooth_angle = 0.20944 + context.scene.objects.active = o + self.load_preset(d) + self.add_material(o) + return o + + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.location = bpy.context.scene.cursor_location + o.select = True + context.scene.objects.active = o + self.manipulate() + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_wall2_from_curve(Operator): + bl_idname = "archipack.wall2_from_curve" + bl_label = "Wall curve" + bl_description = "Create a wall from a curve" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + auto_manipulate = BoolProperty(default=True) + + @classmethod + def poll(self, context): + return context.active_object is not None and context.active_object.type == 'CURVE' + + def create(self, context): + curve = context.active_object + for spline in curve.data.splines: + bpy.ops.archipack.wall2(auto_manipulate=self.auto_manipulate) + o = context.scene.objects.active + d = archipack_wall2.datablock(o) + d.from_spline(curve.matrix_world, 12, spline) + if spline.type == 'POLY': + pt = spline.points[0].co + elif spline.type == 'BEZIER': + pt = spline.bezier_points[0].co + else: + pt = Vector((0, 0, 0)) + # pretranslate + o.matrix_world = curve.matrix_world * Matrix([ + [1, 0, 0, pt.x], + [0, 1, 0, pt.y], + [0, 0, 1, pt.z], + [0, 0, 0, 1] + ]) + return o + + # ----------------------------------------------------- + # Execute + # ----------------------------------------------------- + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + if o is not None: + o.select = True + context.scene.objects.active = o + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_wall2_from_slab(Operator): + bl_idname = "archipack.wall2_from_slab" + bl_label = "->Wall" + bl_description = "Create a wall from a slab" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + auto_manipulate = BoolProperty(default=True) + + @classmethod + def poll(self, context): + o = context.active_object + return o is not None and o.data is not None and 'archipack_slab' in o.data + + def create(self, context): + slab = context.active_object + wd = slab.data.archipack_slab[0] + bpy.ops.archipack.wall2(auto_manipulate=self.auto_manipulate) + o = context.scene.objects.active + d = archipack_wall2.datablock(o) + d.auto_update = False + d.parts.clear() + d.n_parts = wd.n_parts - 1 + d.closed = True + for part in wd.parts: + p = d.parts.add() + if "S_" in part.type: + p.type = "S_WALL" + else: + p.type = "C_WALL" + p.length = part.length + p.radius = part.radius + p.da = part.da + p.a0 = part.a0 + o.select = True + context.scene.objects.active = o + d.auto_update = True + # pretranslate + o.matrix_world = slab.matrix_world.copy() + + bpy.ops.object.select_all(action='DESELECT') + # parenting childs to wall reference point + if o.parent is None: + x, y, z = o.bound_box[0] + context.scene.cursor_location = o.matrix_world * Vector((x, y, z)) + # fix issue #9 + context.scene.objects.active = o + bpy.ops.archipack.reference_point() + else: + o.parent.select = True + context.scene.objects.active = o.parent + o.select = True + slab.select = True + bpy.ops.archipack.parent_to_reference() + o.parent.select = False + return o + + # ----------------------------------------------------- + # Execute + # ----------------------------------------------------- + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.select = True + context.scene.objects.active = o + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +# ------------------------------------------------------------------ +# Define operator class to draw a wall +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator): + bl_idname = "archipack.wall2_draw" + bl_label = "Draw a Wall" + bl_description = "Draw a Wall" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + o = None + state = 'RUNNING' + flag_create = False + flag_next = False + wall_part1 = None + wall_line1 = None + line = None + label = None + feedback = None + takeloc = Vector((0, 0, 0)) + sel = [] + act = None + + # constraint to other wall and make a T child + parent = None + takemat = None + + max_style_draw_tool = False + + @classmethod + def poll(cls, context): + return True + + def draw_callback(self, _self, context): + self.feedback.draw(context) + + def sp_draw(self, sp, context): + z = 2.7 + if self.state == 'CREATE': + p0 = self.takeloc + else: + p0 = sp.takeloc + + p1 = sp.placeloc + delta = p1 - p0 + # print("sp_draw state:%s delta:%s p0:%s p1:%s" % (self.state, delta.length, p0, p1)) + if delta.length == 0: + return + self.wall_part1.set_pos([p0, p1, Vector((p1.x, p1.y, p1.z + z)), Vector((p0.x, p0.y, p0.z + z))]) + self.wall_line1.set_pos([p0, p1, Vector((p1.x, p1.y, p1.z + z)), Vector((p0.x, p0.y, p0.z + z))]) + self.wall_part1.draw(context) + self.wall_line1.draw(context) + self.line.p = p0 + self.line.v = delta + self.label.set_pos(context, self.line.length, self.line.lerp(0.5), self.line.v, normal=Vector((0, 0, 1))) + self.label.draw(context) + self.line.draw(context) + + def sp_callback(self, context, event, state, sp): + # print("sp_callback event %s %s state:%s" % (event.type, event.value, state)) + + if state == 'SUCCESS': + + if self.state == 'CREATE': + takeloc = self.takeloc + delta = sp.placeloc - self.takeloc + else: + takeloc = sp.takeloc + delta = sp.delta + + old = context.active_object + if self.o is None: + bpy.ops.archipack.wall2(auto_manipulate=False) + o = context.active_object + o.location = takeloc + self.o = o + d = archipack_wall2.datablock(o) + part = d.parts[0] + part.length = delta.length + else: + o = self.o + o.select = True + context.scene.objects.active = o + d = archipack_wall2.datablock(o) + # Check for end close to start and close when applicable + dp = sp.placeloc - o.location + if dp.length < 0.01: + d.closed = True + self.state = 'CANCEL' + return + + part = d.add_part(context, delta.length) + + # print("self.o :%s" % o.name) + rM = o.matrix_world.inverted().to_3x3() + g = d.get_generator() + w = g.segs[-2] + dp = rM * delta + da = atan2(dp.y, dp.x) - w.straight(1).angle + a0 = part.a0 + da + if a0 > pi: + a0 -= 2 * pi + if a0 < -pi: + a0 += 2 * pi + part.a0 = a0 + + context.scene.objects.active = old + self.flag_next = True + context.area.tag_redraw() + # print("feedback.on:%s" % self.feedback.on) + + self.state = state + + def sp_init(self, context, event, state, sp): + # print("sp_init event %s %s %s" % (event.type, event.value, state)) + if state == 'SUCCESS': + # point placed, check if a wall was under mouse + res, tM, wall, y = self.mouse_hover_wall(context, event) + if res: + d = archipack_wall2.datablock(wall) + if event.ctrl: + # user snap, use direction as constraint + tM.translation = sp.placeloc.copy() + else: + # without snap, use wall's bottom + tM.translation -= y.normalized() * (0.5 * d.width) + self.takeloc = tM.translation + self.parent = wall.name + self.takemat = tM + else: + self.takeloc = sp.placeloc.copy() + + self.state = 'RUNNING' + # print("feedback.on:%s" % self.feedback.on) + elif state == 'CANCEL': + self.state = state + return + + def ensure_ccw(self): + """ + Wall to slab expect wall vertex order to be ccw + so reverse order here when needed + """ + d = archipack_wall2.datablock(self.o) + g = d.get_generator() + pts = [seg.p0.to_3d() for seg in g.segs] + + if d.closed: + pts.append(pts[0]) + + if d.is_cw(pts): + d.x_offset = 1 + pts = list(reversed(pts)) + self.o.location += pts[0] - pts[-1] + + d.from_points(pts, d.closed) + + def modal(self, context, event): + + context.area.tag_redraw() + # print("modal event %s %s" % (event.type, event.value)) + if event.type == 'NONE': + return {'PASS_THROUGH'} + + if self.state == 'STARTING': + takeloc = self.mouse_to_plane(context, event) + # wait for takeloc being visible when button is over horizon + rv3d = context.region_data + viewinv = rv3d.view_matrix.inverted() + if (takeloc * viewinv).z < 0: + # print("STARTING") + # when user press draw button + snap_point(takeloc=takeloc, + callback=self.sp_init, + # transform_orientation=context.space_data.transform_orientation, + constraint_axis=(True, True, False), + release_confirm=True) + return {'RUNNING_MODAL'} + + elif self.state == 'RUNNING': + # print("RUNNING") + # when user start drawing + + # release confirm = False on blender mode + # release confirm = True on max mode + self.state = 'CREATE' + snap_point(takeloc=self.takeloc, + draw=self.sp_draw, + takemat=self.takemat, + transform_orientation=context.space_data.transform_orientation, + callback=self.sp_callback, + constraint_axis=(True, True, False), + release_confirm=self.max_style_draw_tool) + return {'RUNNING_MODAL'} + + elif self.state != 'CANCEL' and event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}: + + # print('LEFTMOUSE %s' % (event.value)) + self.feedback.instructions(context, "Draw a wall", "Click & Drag to add a segment", [ + ('CTRL', 'Snap'), + ('MMBTN', 'Constraint to axis'), + ('X Y', 'Constraint to axis'), + ('BACK_SPACE', 'Remove part'), + ('RIGHTCLICK or ESC', 'exit') + ]) + + # press with max mode release with blender mode + if self.max_style_draw_tool: + evt_value = 'PRESS' + else: + evt_value = 'RELEASE' + + if event.value == evt_value: + if self.flag_next: + self.flag_next = False + o = self.o + o.select = True + context.scene.objects.active = o + d = archipack_wall2.datablock(o) + g = d.get_generator() + p0 = g.segs[-2].p0 + p1 = g.segs[-2].p1 + dp = p1 - p0 + takemat = o.matrix_world * Matrix([ + [dp.x, dp.y, 0, p1.x], + [dp.y, -dp.x, 0, p1.y], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]) + takeloc = o.matrix_world * p1.to_3d() + o.select = False + else: + takeloc = self.mouse_to_plane(context, event) + takemat = None + + snap_point(takeloc=takeloc, + takemat=takemat, + draw=self.sp_draw, + callback=self.sp_callback, + constraint_axis=(True, True, False), + release_confirm=self.max_style_draw_tool) + + return {'RUNNING_MODAL'} + + if self.keymap.check(event, self.keymap.undo) or ( + event.type in {'BACK_SPACE'} and event.value == 'RELEASE' + ): + if self.o is not None: + o = self.o + o.select = True + context.scene.objects.active = o + d = archipack_wall2.datablock(o) + if d.n_parts > 1: + d.n_parts -= 1 + return {'RUNNING_MODAL'} + + if self.state == 'CANCEL' or (event.type in {'ESC', 'RIGHTMOUSE'} and + event.value == 'RELEASE'): + + self.feedback.disable() + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + + if self.o is None: + context.scene.objects.active = self.act + for o in self.sel: + o.select = True + else: + self.o.select = True + context.scene.objects.active = self.o + d = archipack_wall2.datablock(self.o) + + # remove last segment with blender mode + if not self.max_style_draw_tool: + if not d.closed and d.n_parts > 1: + d.n_parts -= 1 + + self.o.select = True + context.scene.objects.active = self.o + # make T child + if self.parent is not None: + d.t_part = self.parent + + if bpy.ops.archipack.wall2_manipulate.poll(): + bpy.ops.archipack.wall2_manipulate('INVOKE_DEFAULT') + + return {'FINISHED'} + + return {'PASS_THROUGH'} + + def invoke(self, context, event): + + if context.mode == "OBJECT": + prefs = context.user_preferences.addons[__name__.split('.')[0]].preferences + self.max_style_draw_tool = prefs.max_style_draw_tool + self.keymap = Keymaps(context) + self.wall_part1 = GlPolygon((0.5, 0, 0, 0.2)) + self.wall_line1 = GlPolyline((0.5, 0, 0, 0.8)) + self.line = GlLine() + self.label = GlText() + self.feedback = FeedbackPanel() + self.feedback.instructions(context, "Draw a wall", "Click & Drag to start", [ + ('CTRL', 'Snap'), + ('MMBTN', 'Constraint to axis'), + ('X Y', 'Constraint to axis'), + ('SHIFT+CTRL+TAB', 'Switch snap mode'), + ('RIGHTCLICK or ESC', 'exit without change') + ]) + self.feedback.enable() + args = (self, context) + + self.sel = [o for o in context.selected_objects] + self.act = context.active_object + bpy.ops.object.select_all(action="DESELECT") + + self.state = 'STARTING' + + self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback, args, 'WINDOW', 'POST_PIXEL') + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +# ------------------------------------------------------------------ +# Define operator class to manage parts +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_wall2_insert(Operator): + bl_idname = "archipack.wall2_insert" + bl_label = "Insert" + bl_description = "Insert part" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + index = IntProperty(default=0) + + def execute(self, context): + if context.mode == "OBJECT": + o = context.active_object + d = archipack_wall2.datablock(o) + if d is None: + return {'CANCELLED'} + d.insert_part(context, o, self.index) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_wall2_remove(Operator): + bl_idname = "archipack.wall2_remove" + bl_label = "Remove" + bl_description = "Remove part" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + index = IntProperty(default=0) + + def execute(self, context): + if context.mode == "OBJECT": + o = context.active_object + d = archipack_wall2.datablock(o) + if d is None: + return {'CANCELLED'} + d.remove_part(context, o, self.index) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_wall2_reverse(Operator): + bl_idname = "archipack.wall2_reverse" + bl_label = "Reverse" + bl_description = "Reverse parts order" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + if context.mode == "OBJECT": + o = context.active_object + d = archipack_wall2.datablock(o) + if d is None: + return {'CANCELLED'} + d.reverse(context, o) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +# ------------------------------------------------------------------ +# Define operator class to manipulate object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_wall2_manipulate(Operator): + bl_idname = "archipack.wall2_manipulate" + bl_label = "Manipulate" + bl_description = "Manipulate" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_wall2.filter(context.active_object) + + def invoke(self, context, event): + d = archipack_wall2.datablock(context.active_object) + d.manipulable_invoke(context) + return {'FINISHED'} + + def execute(self, context): + """ + For use in boolean ops + """ + if archipack_wall2.filter(context.active_object): + o = context.active_object + d = archipack_wall2.datablock(o) + g = d.get_generator() + d.setup_childs(o, g) + d.update_childs(context, o, g) + d.update(context) + o.select = True + context.scene.objects.active = o + return {'FINISHED'} + + +def register(): + bpy.utils.register_class(archipack_wall2_part) + bpy.utils.register_class(archipack_wall2_child) + bpy.utils.register_class(archipack_wall2) + Mesh.archipack_wall2 = CollectionProperty(type=archipack_wall2) + bpy.utils.register_class(ARCHIPACK_PT_wall2) + bpy.utils.register_class(ARCHIPACK_OT_wall2) + bpy.utils.register_class(ARCHIPACK_OT_wall2_draw) + bpy.utils.register_class(ARCHIPACK_OT_wall2_insert) + bpy.utils.register_class(ARCHIPACK_OT_wall2_remove) + bpy.utils.register_class(ARCHIPACK_OT_wall2_reverse) + bpy.utils.register_class(ARCHIPACK_OT_wall2_manipulate) + bpy.utils.register_class(ARCHIPACK_OT_wall2_from_curve) + bpy.utils.register_class(ARCHIPACK_OT_wall2_from_slab) + bpy.utils.register_class(ARCHIPACK_OT_wall2_throttle_update) + + +def unregister(): + bpy.utils.unregister_class(archipack_wall2_part) + bpy.utils.unregister_class(archipack_wall2_child) + bpy.utils.unregister_class(archipack_wall2) + del Mesh.archipack_wall2 + bpy.utils.unregister_class(ARCHIPACK_PT_wall2) + bpy.utils.unregister_class(ARCHIPACK_OT_wall2) + bpy.utils.unregister_class(ARCHIPACK_OT_wall2_draw) + bpy.utils.unregister_class(ARCHIPACK_OT_wall2_insert) + bpy.utils.unregister_class(ARCHIPACK_OT_wall2_remove) + bpy.utils.unregister_class(ARCHIPACK_OT_wall2_reverse) + bpy.utils.unregister_class(ARCHIPACK_OT_wall2_manipulate) + bpy.utils.unregister_class(ARCHIPACK_OT_wall2_from_curve) + bpy.utils.unregister_class(ARCHIPACK_OT_wall2_from_slab) + bpy.utils.unregister_class(ARCHIPACK_OT_wall2_throttle_update) diff --git a/archipack/archipack_window.py b/archipack/archipack_window.py new file mode 100644 index 000000000..2be559474 --- /dev/null +++ b/archipack/archipack_window.py @@ -0,0 +1,2098 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +# noinspection PyUnresolvedReferences +import bpy +# noinspection PyUnresolvedReferences +from bpy.types import Operator, PropertyGroup, Mesh, Panel +from bpy.props import ( + FloatProperty, IntProperty, BoolProperty, BoolVectorProperty, + CollectionProperty, FloatVectorProperty, EnumProperty, StringProperty +) +from mathutils import Vector +from math import tan, sqrt +from .bmesh_utils import BmeshEdit as bmed +from .panel import Panel as WindowPanel +from .materialutils import MaterialUtils +from .archipack_handle import create_handle, window_handle_vertical_01, window_handle_vertical_02 +# from .archipack_door_panel import ARCHIPACK_OT_select_parent +from .archipack_manipulator import Manipulable +from .archipack_preset import ArchipackPreset, PresetMenuOperator +from .archipack_gl import FeedbackPanel +from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchpackDrawTool +from .archipack_keymaps import Keymaps + + +def update(self, context): + self.update(context) + + +def update_childs(self, context): + self.update(context, childs_only=True) + + +def set_cols(self, value): + if self.n_cols != value: + self.auto_update = False + self._set_width(value) + self.auto_update = True + self.n_cols = value + return None + + +def get_cols(self): + return self.n_cols + + +class archipack_window_panelrow(PropertyGroup): + width = FloatVectorProperty( + name="width", + min=0.5, + max=100.0, + default=[ + 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50 + ], + size=31, + update=update + ) + fixed = BoolVectorProperty( + name="Fixed", + default=[ + False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False + ], + size=32, + update=update + ) + cols = IntProperty( + name="panels", + description="number of panels getter and setter, to avoid infinite recursion", + min=1, + max=32, + default=2, + get=get_cols, set=set_cols + ) + n_cols = IntProperty( + name="panels", + description="store number of panels, internal use only to avoid infinite recursion", + min=1, + max=32, + default=2, + update=update + ) + height = FloatProperty( + name="Height", + min=0.1, + default=1.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + auto_update = BoolProperty( + options={'SKIP_SAVE'}, + name="auto_update", + description="disable auto update to avoid infinite recursion", + default=True + ) + + def get_row(self, x, y): + size = [Vector((x * self.width[w] / 100, y, 0)) for w in range(self.cols - 1)] + sum_x = sum([s.x for s in size]) + size.append(Vector((x - sum_x, y, 0))) + origin = [] + pivot = [] + ttl = 0 + xh = x / 2 + n_center = len(size) / 2 + for i, sx in enumerate(size): + ttl += sx.x + if i < n_center: + # pivot left + origin.append(Vector((ttl - xh - sx.x, 0))) + pivot.append(1) + else: + # pivot right + origin.append(Vector((ttl - xh, 0))) + pivot.append(-1) + return size, origin, pivot + + def _set_width(self, cols): + width = 100 / cols + for i in range(cols - 1): + self.width[i] = width + + def find_datablock_in_selection(self, context): + """ + find witch selected object this instance belongs to + provide support for "copy to selected" + """ + selected = [o for o in context.selected_objects] + for o in selected: + props = archipack_window.datablock(o) + if props: + for row in props.rows: + if row == self: + return props + return None + + def update(self, context): + if self.auto_update: + props = self.find_datablock_in_selection(context) + if props is not None: + props.update(context, childs_only=False) + + def draw(self, layout, context, last_row): + # store parent at runtime to trigger update on parent + row = layout.row() + row.prop(self, "cols") + row = layout.row() + if not last_row: + row.prop(self, "height") + for i in range(self.cols - 1): + row = layout.row() + row.prop(self, "width", text="col " + str(i + 1), index=i) + row.prop(self, "fixed", text="fixed", index=i) + row = layout.row() + row.label(text="col " + str(self.cols)) + row.prop(self, "fixed", text="fixed", index=(self.cols - 1)) + + +class archipack_window_panel(ArchipackObject, PropertyGroup): + center = FloatVectorProperty( + subtype='XYZ' + ) + origin = FloatVectorProperty( + subtype='XYZ' + ) + size = FloatVectorProperty( + subtype='XYZ' + ) + radius = FloatVectorProperty( + subtype='XYZ' + ) + angle_y = FloatProperty( + name='angle', + unit='ROTATION', + subtype='ANGLE', + min=-1.5, max=1.5, + default=0, precision=2, + description='angle' + ) + frame_y = FloatProperty( + name='Depth', + min=0, + default=0.06, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='frame depth' + ) + frame_x = FloatProperty( + name='Width', + min=0, + default=0.06, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='frame width' + ) + curve_steps = IntProperty( + name="curve steps", + min=1, + max=128, + default=1 + ) + shape = EnumProperty( + name='Shape', + items=( + ('RECTANGLE', 'Rectangle', '', 0), + ('ROUND', 'Top Round', '', 1), + ('ELLIPSIS', 'Top elliptic', '', 2), + ('QUADRI', 'Top oblique', '', 3), + ('CIRCLE', 'Full circle', '', 4) + ), + default='RECTANGLE' + ) + pivot = FloatProperty( + name='pivot', + min=-1, max=1, + default=-1, precision=2, + description='pivot' + ) + side_material = IntProperty( + name="side material", + min=0, + max=2, + default=0 + ) + handle = EnumProperty( + name='Shape', + items=( + ('NONE', 'No handle', '', 0), + ('INSIDE', 'Inside', '', 1), + ('BOTH', 'Inside and outside', '', 2) + ), + default='NONE' + ) + handle_model = IntProperty( + name="handle model", + default=1, + min=1, + max=2 + ) + handle_altitude = FloatProperty( + name='handle altitude', + min=0, + default=0.2, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='handle altitude' + ) + fixed = BoolProperty( + name="Fixed", + default=False + ) + + @property + def window(self): + verre = 0.005 + chanfer = 0.004 + x0 = 0 + x1 = self.frame_x + x2 = 0.75 * self.frame_x + x3 = chanfer + y0 = -self.frame_y + y1 = 0 + y2 = -0.5 * self.frame_y + y3 = -chanfer + y4 = chanfer - self.frame_y + + if self.fixed: + # profil carre avec support pour verre + # p ______ y1 + # / | y3 + # | |___ + # x |___ y2 verre + # | | y4 + # \______| y0 + # x0 x3 x1 + # + x1 = 0.5 * self.frame_x + y1 = -0.45 * self.frame_y + y3 = y1 - chanfer + y4 = chanfer + y0 + y2 = (y0 + y2) / 2 + return WindowPanel( + True, # closed + [1, 0, 0, 0, 1, 2, 2, 2, 2], # x index + [x0, x3, x1], + [y0, y4, y2, y3, y1, y1, y2 + verre, y2 - verre, y0], + [0, 0, 1, 1, 1, 1, 0, 0, 0], # materials + side_cap_front=6, + side_cap_back=7 # cap index + ) + else: + # profil avec chanfrein et joint et support pour verre + # p ____ y1 inside + # / |_ y3 + # | |___ + # x |___ y2 verre + # | _| y4 + # \____| y0 + # x0 x3 x2 x1 outside + if self.side_material == 0: + materials = [0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0] + elif self.side_material == 1: + # rail window exterior + materials = [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0] + else: + # rail window interior + materials = [0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0] + return WindowPanel( + True, # closed shape + [1, 0, 0, 0, 1, 2, 2, 3, 3, 3, 3, 2, 2], # x index + [x0, x3, x2, x1], # unique x positions + [y0, y4, y2, y3, y1, y1, y3, y3, y2 + verre, y2 - verre, y4, y4, y0], + materials, # materials + side_cap_front=8, + side_cap_back=9 # cap index + ) + + @property + def verts(self): + offset = Vector((0, 0, 0)) + return self.window.vertices(self.curve_steps, offset, self.center, self.origin, self.size, + self.radius, self.angle_y, self.pivot, shape_z=None, path_type=self.shape) + + @property + def faces(self): + return self.window.faces(self.curve_steps, path_type=self.shape) + + @property + def matids(self): + return self.window.mat(self.curve_steps, 2, 2, path_type=self.shape) + + @property + def uvs(self): + return self.window.uv(self.curve_steps, self.center, self.origin, self.size, + self.radius, self.angle_y, self.pivot, 0, self.frame_x, path_type=self.shape) + + def find_handle(self, o): + for child in o.children: + if 'archipack_handle' in child: + return child + return None + + def update_handle(self, context, o): + handle = self.find_handle(o) + if handle is None: + m = bpy.data.meshes.new("Handle") + handle = create_handle(context, o, m) + MaterialUtils.add_handle_materials(handle) + if self.handle_model == 1: + verts, faces = window_handle_vertical_01(1) + else: + verts, faces = window_handle_vertical_02(1) + handle.location = (self.pivot * (self.size.x - 0.4 * self.frame_x), 0, self.handle_altitude) + bmed.buildmesh(context, handle, verts, faces) + + def remove_handle(self, context, o): + handle = self.find_handle(o) + if handle is not None: + context.scene.objects.unlink(handle) + bpy.data.objects.remove(handle, do_unlink=True) + + def update(self, context): + + o = self.find_in_selection(context) + + if o is None: + return + + # update handle, dosent care of instances as window will do + if self.handle == 'NONE': + self.remove_handle(context, o) + else: + self.update_handle(context, o) + + bmed.buildmesh(context, o, self.verts, self.faces, self.matids, self.uvs) + + self.restore_context(context) + + +class archipack_window(ArchipackObject, Manipulable, PropertyGroup): + x = FloatProperty( + name='width', + min=0.25, + default=100.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='Width', update=update + ) + y = FloatProperty( + name='depth', + min=0.1, + default=0.20, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='Depth', update=update, + ) + z = FloatProperty( + name='height', + min=0.1, + default=1.2, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='height', update=update, + ) + angle_y = FloatProperty( + name='angle', + unit='ROTATION', + subtype='ANGLE', + min=-1.5, max=1.5, + default=0, precision=2, + description='angle', update=update, + ) + radius = FloatProperty( + name='radius', + min=0.1, + default=2.5, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='radius', update=update, + ) + elipsis_b = FloatProperty( + name='ellipsis', + min=0.1, + default=0.5, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='ellipsis vertical size', update=update, + ) + altitude = FloatProperty( + name='altitude', + default=1.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='altitude', update=update, + ) + offset = FloatProperty( + name='offset', + default=0.1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='offset', update=update, + ) + frame_y = FloatProperty( + name='Depth', + min=0, + default=0.06, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='frame depth', update=update, + ) + frame_x = FloatProperty( + name='Width', + min=0, + default=0.06, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='frame width', update=update, + ) + out_frame = BoolProperty( + name="Out frame", + default=False, update=update, + ) + out_frame_y = FloatProperty( + name='Side depth', + min=0.001, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='frame side depth', update=update, + ) + out_frame_y2 = FloatProperty( + name='Front depth', + min=0.001, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='frame front depth', update=update, + ) + out_frame_x = FloatProperty( + name='Front Width', + min=0.0, + default=0.1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='frame width set to 0 disable front frame', update=update, + ) + out_frame_offset = FloatProperty( + name='offset', + min=0.0, + default=0.0, precision=3, step=0.1, + unit='LENGTH', subtype='DISTANCE', + description='frame offset', update=update, + ) + out_tablet_enable = BoolProperty( + name="Out tablet", + default=True, update=update, + ) + out_tablet_x = FloatProperty( + name='Width', + min=0.0, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='tablet width', update=update, + ) + out_tablet_y = FloatProperty( + name='Depth', + min=0.001, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='tablet depth', update=update, + ) + out_tablet_z = FloatProperty( + name='Height', + min=0.001, + default=0.03, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='tablet height', update=update, + ) + in_tablet_enable = BoolProperty( + name="In tablet", + default=True, update=update, + ) + in_tablet_x = FloatProperty( + name='Width', + min=0.0, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='tablet width', update=update, + ) + in_tablet_y = FloatProperty( + name='Depth', + min=0.001, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='tablet depth', update=update, + ) + in_tablet_z = FloatProperty( + name='Height', + min=0.001, + default=0.03, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='tablet height', update=update, + ) + blind_enable = BoolProperty( + name="Blind", + default=False, update=update, + ) + blind_y = FloatProperty( + name='Depth', + min=0.001, + default=0.002, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='Store depth', update=update, + ) + blind_z = FloatProperty( + name='Height', + min=0.001, + default=0.03, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='Store height', update=update, + ) + blind_open = FloatProperty( + name='Open', + min=0.0, max=100, + default=80, precision=1, + subtype='PERCENTAGE', + description='Store open', update=update, + ) + rows = CollectionProperty(type=archipack_window_panelrow) + n_rows = IntProperty( + name="number of rows", + min=1, + max=32, + default=1, update=update, + ) + curve_steps = IntProperty( + name="curve steps", + min=6, + max=128, + default=16, update=update, + ) + hole_outside_mat = IntProperty( + name="Outside", + min=0, + max=128, + default=0, update=update, + ) + hole_inside_mat = IntProperty( + name="Inside", + min=0, + max=128, + default=1, update=update, + ) + window_shape = EnumProperty( + name='Shape', + items=( + ('RECTANGLE', 'Rectangle', '', 0), + ('ROUND', 'Top Round', '', 1), + ('ELLIPSIS', 'Top elliptic', '', 2), + ('QUADRI', 'Top oblique', '', 3), + ('CIRCLE', 'Full circle', '', 4) + ), + default='RECTANGLE', update=update, + ) + window_type = EnumProperty( + name='Type', + items=( + ('FLAT', 'Flat window', '', 0), + ('RAIL', 'Rail window', '', 1) + ), + default='FLAT', update=update, + ) + warning = BoolProperty( + name="warning", + default=False + ) + handle_enable = BoolProperty( + name='handle', + default=True, update=update_childs, + ) + handle_altitude = FloatProperty( + name="altitude", + min=0, + default=1.4, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='handle altitude', update=update_childs, + ) + hole_margin = FloatProperty( + name='hole margin', + min=0.0, + default=0.1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='how much hole surround wall' + ) + flip = BoolProperty( + default=False, + update=update, + description='flip outside and outside material of hole' + ) + # layout related + display_detail = BoolProperty( + options={'SKIP_SAVE'}, + default=False + ) + display_panels = BoolProperty( + options={'SKIP_SAVE'}, + default=True + ) + display_materials = BoolProperty( + options={'SKIP_SAVE'}, + default=True + ) + + auto_update = BoolProperty( + options={'SKIP_SAVE'}, + default=True, + update=update + ) + + @property + def shape(self): + if self.window_type == 'RAIL': + return 'RECTANGLE' + else: + return self.window_shape + + @property + def window(self): + # Flat window frame profil + # ___ y1 + # | |__ + # | | y2 + # |______| y0 + # + x0 = 0 + x1 = -x0 - self.frame_x + x2 = x0 + 0.5 * self.frame_x + y0 = 0.5 * self.y - self.offset + y2 = y0 + 0.5 * self.frame_y + + if self.window_type == 'FLAT': + y1 = y0 + self.frame_y + return WindowPanel( + True, # closed + [0, 0, 1, 1, 2, 2], # x index + [x1, x0, x2], + [y0, y1, y1, y2, y2, y0], + [1, 1, 1, 1, 0, 0] # material index + ) + else: + # Rail window frame profil + # ________ y1 + # | __| y5 + # | |__ y4 + # | __| y3 + # | |____ + # | | y2 + # |__________| y0 + # -x1 0 x3 x2 + x2 = x0 + 0.35 * self.frame_y + x3 = x0 + 0.2 * self.frame_x + y1 = y0 + 2.55 * self.frame_y + y3 = y0 + 1.45 * self.frame_y + y4 = y0 + 1.55 * self.frame_y + y5 = y0 + 2.45 * self.frame_y + + return WindowPanel( + True, # closed + [0, 0, 2, 2, 1, 1, 2, 2, 1, 1, 3, 3], # x index + [x1, x0, x3, x2], + [y0, y1, y1, y5, y5, y4, y4, y3, y3, y2, y2, y0], + [1, 1, 1, 3, 1, 3, 3, 3, 0, 3, 0, 0] # material index + ) + + @property + def hole(self): + # profil percement ____ + # _____ y_inside vertical ___| x1 + # | + # |__ y0 outside ___ + # |___ y_outside |____ x1-shape_z inside + # -x1 x0 + y0 = 0.5 * self.y - self.offset + x1 = self.frame_x # sur-largeur percement interieur + y_inside = 0.5 * self.y + self.hole_margin # outside wall + + if self.out_frame is False: + x0 = 0 + else: + x0 = -min(self.frame_x - 0.001, self.out_frame_y + self.out_frame_offset) + + outside_mat = self.hole_outside_mat + inside_mat = self.hole_inside_mat + # if self.flip: + # outside_mat, inside_mat = inside_mat, outside_mat + + y_outside = -y_inside # inside wall + + return WindowPanel( + False, # closed + [1, 1, 0, 0], # x index + [-x1, x0], + [y_outside, y0, y0, y_inside], + [outside_mat, outside_mat, inside_mat], # material index + side_cap_front=3, # cap index + side_cap_back=0 + ) + + @property + def frame(self): + # profil cadre + # ___ y0 + # __| | + # | | y2 + # |______| y1 + # x1 x2 x0 + y2 = -0.5 * self.y + y0 = 0.5 * self.y - self.offset + y1 = y2 - self.out_frame_y2 + x0 = 0 # -min(self.frame_x - 0.001, self.out_frame_offset) + x1 = x0 - self.out_frame_x + x2 = x0 - self.out_frame_y + # y = depth + # x = width + if self.out_frame_x <= self.out_frame_y: + if self.out_frame_x == 0: + pts_y = [y2, y0, y0, y2] + else: + pts_y = [y1, y0, y0, y1] + return WindowPanel( + True, # closed profil + [0, 0, 1, 1], # x index + [x2, x0], + pts_y, + [0, 0, 0, 0], # material index + closed_path=bool(self.shape == 'CIRCLE') # closed path + ) + else: + return WindowPanel( + True, # closed profil + [0, 0, 1, 1, 2, 2], # x index + [x1, x2, x0], + [y1, y2, y2, y0, y0, y1], + [0, 0, 0, 0, 0, 0], # material index + closed_path=bool(self.shape == 'CIRCLE') # closed path + ) + + @property + def out_tablet(self): + # profil tablette + # __ y0 + # | | y2 + # | / y3 + # |_| y1 + # x0 x2 x1 + y0 = 0.001 + 0.5 * self.y - self.offset + y1 = -0.5 * self.y - self.out_tablet_y + y2 = y0 - 0.01 + y3 = y2 - 0.04 + x2 = 0 + x0 = x2 - self.out_tablet_z + x1 = 0.3 * self.frame_x + # y = depth + # x = width1 + return WindowPanel( + True, # closed profil + [1, 1, 2, 2, 0, 0], # x index + [x0, x2, x1], + [y1, y3, y2, y0, y0, y1], + [4, 3, 3, 4, 4, 4], # material index + closed_path=False # closed path + ) + + @property + def in_tablet(self): + # profil tablette + # __ y0 + # | | + # | | + # |__| y1 + # x0 x1 + y0 = 0.5 * self.y + self.frame_y - self.offset + y1 = 0.5 * self.y + self.in_tablet_y + if self.window_type == 'RAIL': + y0 += 1.55 * self.frame_y + y1 += 1.55 * self.frame_y + x0 = -self.frame_x + x1 = min(x0 + self.in_tablet_z, x0 + self.frame_x - 0.001) + # y = depth + # x = width1 + return WindowPanel( + True, # closed profil + [0, 0, 1, 1], # x index + [x0, x1], + [y1, y0, y0, y1], + [1, 1, 1, 1], # material index + closed_path=False # closed path + ) + + @property + def blind(self): + # profil blind + # y0 + # | / | / | / + # y1 + # xn x1 x0 + dx = self.z / self.blind_z + nx = int(self.z / dx) + y0 = -0.5 * self.offset + # -0.5 * self.y + 0.5 * (0.5 * self.y - self.offset) + # 0.5 * (-0.5 * self.y-0.5 * self.offset) + y1 = y0 + self.blind_y + nx = int(self.z / self.blind_z) + dx = self.z / nx + open = 1.0 - self.blind_open / 100 + return WindowPanel( + False, # profil closed + [int((i + (i % 2)) / 2) for i in range(2 * nx)], # x index + [self.z - (dx * i * open) for i in range(nx + 1)], # x + [[y1, y0][i % 2] for i in range(2 * nx)], # + [5 for i in range(2 * nx - 1)], # material index + closed_path=False # + ) + + @property + def verts(self): + center, origin, size, radius = self.get_radius() + is_not_circle = self.shape != 'CIRCLE' + offset = Vector((0, self.altitude, 0)) + verts = self.window.vertices(self.curve_steps, offset, center, origin, + size, radius, self.angle_y, 0, shape_z=None, path_type=self.shape) + if self.out_frame: + verts += self.frame.vertices(self.curve_steps, offset, center, origin, + size, radius, self.angle_y, 0, shape_z=None, path_type=self.shape) + if is_not_circle and self.out_tablet_enable: + verts += self.out_tablet.vertices(self.curve_steps, offset, center, origin, + Vector((size.x + 2 * self.out_tablet_x, size.y, size.z)), + radius, self.angle_y, 0, shape_z=None, path_type='HORIZONTAL') + if is_not_circle and self.in_tablet_enable: + verts += self.in_tablet.vertices(self.curve_steps, offset, center, origin, + Vector((size.x + 2 * (self.frame_x + self.in_tablet_x), size.y, size.z)), + radius, self.angle_y, 0, shape_z=None, path_type='HORIZONTAL') + if is_not_circle and self.blind_enable: + verts += self.blind.vertices(self.curve_steps, offset, center, origin, + Vector((-size.x, 0, 0)), radius, 0, 0, shape_z=None, path_type='HORIZONTAL') + return verts + + @property + def faces(self): + window = self.window + faces = window.faces(self.curve_steps, path_type=self.shape) + verts_offset = window.n_verts(self.curve_steps, path_type=self.shape) + is_not_circle = self.shape != 'CIRCLE' + if self.out_frame: + frame = self.frame + faces += frame.faces(self.curve_steps, path_type=self.shape, offset=verts_offset) + verts_offset += frame.n_verts(self.curve_steps, path_type=self.shape) + if is_not_circle and self.out_tablet_enable: + tablet = self.out_tablet + faces += tablet.faces(self.curve_steps, path_type='HORIZONTAL', offset=verts_offset) + verts_offset += tablet.n_verts(self.curve_steps, path_type='HORIZONTAL') + if is_not_circle and self.in_tablet_enable: + tablet = self.in_tablet + faces += tablet.faces(self.curve_steps, path_type='HORIZONTAL', offset=verts_offset) + verts_offset += tablet.n_verts(self.curve_steps, path_type='HORIZONTAL') + if is_not_circle and self.blind_enable: + blind = self.blind + faces += blind.faces(self.curve_steps, path_type='HORIZONTAL', offset=verts_offset) + verts_offset += blind.n_verts(self.curve_steps, path_type='HORIZONTAL') + + return faces + + @property + def matids(self): + mat = self.window.mat(self.curve_steps, 2, 2, path_type=self.shape) + is_not_circle = self.shape != 'CIRCLE' + if self.out_frame: + mat += self.frame.mat(self.curve_steps, 0, 0, path_type=self.shape) + if is_not_circle and self.out_tablet_enable: + mat += self.out_tablet.mat(self.curve_steps, 0, 0, path_type='HORIZONTAL') + if is_not_circle and self.in_tablet_enable: + mat += self.in_tablet.mat(self.curve_steps, 0, 0, path_type='HORIZONTAL') + if is_not_circle and self.blind_enable: + mat += self.blind.mat(self.curve_steps, 0, 0, path_type='HORIZONTAL') + return mat + + @property + def uvs(self): + center, origin, size, radius = self.get_radius() + uvs = self.window.uv(self.curve_steps, center, origin, size, radius, + self.angle_y, 0, 0, self.frame_x, path_type=self.shape) + is_not_circle = self.shape != 'CIRCLE' + if self.out_frame: + uvs += self.frame.uv(self.curve_steps, center, origin, size, radius, + self.angle_y, 0, 0, self.frame_x, path_type=self.shape) + if is_not_circle and self.out_tablet_enable: + uvs += self.out_tablet.uv(self.curve_steps, center, origin, size, radius, + self.angle_y, 0, 0, self.frame_x, path_type='HORIZONTAL') + if is_not_circle and self.in_tablet_enable: + uvs += self.in_tablet.uv(self.curve_steps, center, origin, size, radius, + self.angle_y, 0, 0, self.frame_x, path_type='HORIZONTAL') + if is_not_circle and self.blind_enable: + uvs += self.blind.uv(self.curve_steps, center, origin, size, radius, + self.angle_y, 0, 0, self.frame_x, path_type='HORIZONTAL') + return uvs + + def setup_manipulators(self): + if len(self.manipulators) == 4: + return + s = self.manipulators.add() + s.prop1_name = "x" + s.prop2_name = "x" + s.type_key = "SNAP_SIZE_LOC" + s = self.manipulators.add() + s.prop1_name = "y" + s.prop2_name = "y" + s.type_key = "SNAP_SIZE_LOC" + s = self.manipulators.add() + s.prop1_name = "z" + s.normal = Vector((0, 1, 0)) + s = self.manipulators.add() + s.prop1_name = "altitude" + s.normal = Vector((0, 1, 0)) + + def remove_childs(self, context, o, to_remove): + for child in o.children: + if to_remove < 1: + return + if archipack_window_panel.filter(child): + to_remove -= 1 + self.remove_handle(context, child) + context.scene.objects.unlink(child) + bpy.data.objects.remove(child, do_unlink=True) + + def remove_handle(self, context, o): + handle = self.find_handle(o) + if handle is not None: + context.scene.objects.unlink(handle) + bpy.data.objects.remove(handle, do_unlink=True) + + def update_rows(self, context, o): + # remove rows + for i in range(len(self.rows), self.n_rows, -1): + self.rows.remove(i - 1) + + # add rows + for i in range(len(self.rows), self.n_rows): + self.rows.add() + + # wanted childs + if self.shape == 'CIRCLE': + w_childs = 1 + elif self.window_type == 'RAIL': + w_childs = self.rows[0].cols + else: + w_childs = sum([row.cols for row in self.rows]) + + # real childs + childs = self.get_childs_panels(context, o) + n_childs = len(childs) + + # remove child + if n_childs > w_childs: + self.remove_childs(context, o, n_childs - w_childs) + + def get_childs_panels(self, context, o): + return [child for child in o.children if archipack_window_panel.filter(child)] + + def adjust_size_and_origin(self, size, origin, pivot, materials): + if len(size) > 1: + size[0].x += 0.5 * self.frame_x + size[-1].x += 0.5 * self.frame_x + for i in range(1, len(size) - 1): + size[i].x += 0.5 * self.frame_x + origin[i].x += -0.25 * self.frame_x * pivot[i] + for i, o in enumerate(origin): + o.y = (1 - (i % 2)) * self.frame_y + for i, o in enumerate(origin): + materials[i] = (1 - (i % 2)) + 1 + + def find_handle(self, o): + for handle in o.children: + if 'archipack_handle' in handle: + return handle + return None + + def _synch_childs(self, context, o, linked, childs): + """ + sub synch childs nodes of linked object + """ + # remove childs not found on source + l_childs = self.get_childs_panels(context, linked) + c_names = [c.data.name for c in childs] + for c in l_childs: + try: + id = c_names.index(c.data.name) + except: + self.remove_handle(context, c) + context.scene.objects.unlink(c) + bpy.data.objects.remove(c, do_unlink=True) + + # children ordering may not be the same, so get the right l_childs order + l_childs = self.get_childs_panels(context, linked) + l_names = [c.data.name for c in l_childs] + order = [] + for c in childs: + try: + id = l_names.index(c.data.name) + except: + id = -1 + order.append(id) + + # add missing childs and update other ones + for i, child in enumerate(childs): + if order[i] < 0: + p = bpy.data.objects.new("Panel", child.data) + context.scene.objects.link(p) + p.lock_location[0] = True + p.lock_location[1] = True + p.lock_location[2] = True + p.lock_rotation[1] = True + p.lock_scale[0] = True + p.lock_scale[1] = True + p.lock_scale[2] = True + p.parent = linked + p.matrix_world = linked.matrix_world.copy() + + else: + p = l_childs[order[i]] + + # update handle + handle = self.find_handle(child) + h = self.find_handle(p) + if handle is not None: + if h is None: + h = create_handle(context, p, handle.data) + MaterialUtils.add_handle_materials(h) + h.location = handle.location.copy() + elif h is not None: + context.scene.objects.unlink(h) + bpy.data.objects.remove(h, do_unlink=True) + + p.location = child.location.copy() + + def _synch_hole(self, context, linked, hole): + l_hole = self.find_hole(linked) + if l_hole is None: + l_hole = bpy.data.objects.new("hole", hole.data) + l_hole['archipack_hole'] = True + context.scene.objects.link(l_hole) + l_hole.parent = linked + l_hole.matrix_world = linked.matrix_world.copy() + l_hole.location = hole.location.copy() + else: + l_hole.data = hole.data + + def synch_childs(self, context, o): + """ + synch childs nodes of linked objects + """ + bpy.ops.object.select_all(action='DESELECT') + o.select = True + context.scene.objects.active = o + childs = self.get_childs_panels(context, o) + hole = self.find_hole(o) + bpy.ops.object.select_linked(type='OBDATA') + for linked in context.selected_objects: + if linked != o: + self._synch_childs(context, o, linked, childs) + if hole is not None: + self._synch_hole(context, linked, hole) + + def update_childs(self, context, o): + """ + pass params to childrens + """ + self.update_rows(context, o) + childs = self.get_childs_panels(context, o) + n_childs = len(childs) + child_n = 0 + row_n = 0 + location_y = 0.5 * self.y - self.offset + 0.5 * self.frame_y + center, origin, size, radius = self.get_radius() + offset = Vector((0, 0)) + handle = 'NONE' + if self.shape != 'CIRCLE': + if self.handle_enable: + if self.z > 1.8: + handle = 'BOTH' + else: + handle = 'INSIDE' + is_circle = False + else: + is_circle = True + + if self.window_type == 'RAIL': + handle_model = 2 + else: + handle_model = 1 + + for row in self.rows: + row_n += 1 + if row_n < self.n_rows and not is_circle and self.window_type != 'RAIL': + z = row.height + shape = 'RECTANGLE' + else: + z = max(2 * self.frame_x + 0.001, self.z - offset.y) + shape = self.shape + + self.warning = bool(z > self.z - offset.y) + if self.warning: + break + size, origin, pivot = row.get_row(self.x, z) + # side materials + materials = [0 for i in range(row.cols)] + + handle_altitude = min( + max(4 * self.frame_x, self.handle_altitude - offset.y - self.altitude), + z - 4 * self.frame_x + ) + + if self.window_type == 'RAIL': + self.adjust_size_and_origin(size, origin, pivot, materials) + + for panel in range(row.cols): + child_n += 1 + + if row.fixed[panel]: + enable_handle = 'NONE' + else: + enable_handle = handle + + if child_n > n_childs: + bpy.ops.archipack.window_panel( + center=center, + origin=Vector((origin[panel].x, offset.y, 0)), + size=size[panel], + radius=radius, + pivot=pivot[panel], + shape=shape, + fixed=row.fixed[panel], + handle=enable_handle, + handle_model=handle_model, + handle_altitude=handle_altitude, + curve_steps=self.curve_steps, + side_material=materials[panel], + frame_x=self.frame_x, + frame_y=self.frame_y, + angle_y=self.angle_y, + ) + child = context.active_object + # parenting at 0, 0, 0 before set object matrix_world + # so location remains local from frame + child.parent = o + child.matrix_world = o.matrix_world.copy() + else: + child = childs[child_n - 1] + child.select = True + context.scene.objects.active = child + props = archipack_window_panel.datablock(child) + if props is not None: + props.origin = Vector((origin[panel].x, offset.y, 0)) + props.center = center + props.radius = radius + props.size = size[panel] + props.pivot = pivot[panel] + props.shape = shape + props.fixed = row.fixed[panel] + props.handle = enable_handle + props.handle_model = handle_model + props.handle_altitude = handle_altitude + props.side_material = materials[panel] + props.curve_steps = self.curve_steps + props.frame_x = self.frame_x + props.frame_y = self.frame_y + props.angle_y = self.angle_y + props.update(context) + # location y + frame width. frame depends on choosen profile (fixed or not) + # update linked childs location too + child.location = Vector((origin[panel].x, origin[panel].y + location_y + self.frame_y, + self.altitude + offset.y)) + + if not row.fixed[panel]: + handle = 'NONE' + + # only one single panel allowed for circle + if is_circle: + return + + # only one single row allowed for rail window + if self.window_type == 'RAIL': + return + offset.y += row.height + + def _get_tri_radius(self): + return Vector((0, self.y, 0)), Vector((0, 0, 0)), \ + Vector((self.x, self.z, 0)), Vector((self.x, 0, 0)) + + def _get_quad_radius(self): + fx_z = self.z / self.x + center_y = min(self.x / (self.x - self.frame_x) * self.z - self.frame_x * (1 + sqrt(1 + fx_z * fx_z)), + abs(tan(self.angle_y) * (self.x))) + if self.angle_y < 0: + center_x = 0.5 * self.x + else: + center_x = -0.5 * self.x + return Vector((center_x, center_y, 0)), Vector((0, 0, 0)), \ + Vector((self.x, self.z, 0)), Vector((self.x, 0, 0)) + + def _get_round_radius(self): + """ + bound radius to available space + return center, origin, size, radius + """ + x = 0.5 * self.x - self.frame_x + # minimum space available + y = self.z - sum([row.height for row in self.rows[:self.n_rows - 1]]) - 2 * self.frame_x + y = min(y, x) + # minimum radius inside + r = y + x * (x - (y * y / x)) / (2 * y) + radius = max(self.radius, 0.001 + self.frame_x + r) + return Vector((0, self.z - radius, 0)), Vector((0, 0, 0)), \ + Vector((self.x, self.z, 0)), Vector((radius, 0, 0)) + + def _get_circle_radius(self): + """ + return center, origin, size, radius + """ + return Vector((0, 0.5 * self.x, 0)), Vector((0, 0, 0)), \ + Vector((self.x, self.z, 0)), Vector((0.5 * self.x, 0, 0)) + + def _get_ellipsis_radius(self): + """ + return center, origin, size, radius + """ + y = self.z - sum([row.height for row in self.rows[:self.n_rows - 1]]) + radius_b = max(0, 0.001 - 2 * self.frame_x + min(y, self.elipsis_b)) + return Vector((0, self.z - radius_b, 0)), Vector((0, 0, 0)), \ + Vector((self.x, self.z, 0)), Vector((self.x / 2, radius_b, 0)) + + def get_radius(self): + """ + return center, origin, size, radius + """ + if self.shape == 'ROUND': + return self._get_round_radius() + elif self.shape == 'ELLIPSIS': + return self._get_ellipsis_radius() + elif self.shape == 'CIRCLE': + return self._get_circle_radius() + elif self.shape == 'QUADRI': + return self._get_quad_radius() + elif self.shape in ['TRIANGLE', 'PENTAGON']: + return self._get_tri_radius() + else: + return Vector((0, 0, 0)), Vector((0, 0, 0)), \ + Vector((self.x, self.z, 0)), Vector((0, 0, 0)) + + def update(self, context, childs_only=False): + # support for "copy to selected" + o = self.find_in_selection(context, self.auto_update) + + if o is None: + return + + self.setup_manipulators() + + if childs_only is False: + bmed.buildmesh(context, o, self.verts, self.faces, self.matids, self.uvs) + + self.update_childs(context, o) + + # update hole + if childs_only is False and self.find_hole(o) is not None: + self.interactive_hole(context, o) + + # support for instances childs, update at object level + self.synch_childs(context, o) + + # store 3d points for gl manipulators + x, y = 0.5 * self.x, 0.5 * self.y + self.manipulators[0].set_pts([(-x, -y, 0), (x, -y, 0), (1, 0, 0)]) + self.manipulators[1].set_pts([(-x, -y, 0), (-x, y, 0), (-1, 0, 0)]) + self.manipulators[2].set_pts([(x, -y, self.altitude), (x, -y, self.altitude + self.z), (-1, 0, 0)]) + self.manipulators[3].set_pts([(x, -y, 0), (x, -y, self.altitude), (-1, 0, 0)]) + + # restore context + self.restore_context(context) + + def find_hole(self, o): + for child in o.children: + if 'archipack_hole' in child: + return child + return None + + def interactive_hole(self, context, o): + hole_obj = self.find_hole(o) + + if hole_obj is None: + m = bpy.data.meshes.new("hole") + hole_obj = bpy.data.objects.new("hole", m) + context.scene.objects.link(hole_obj) + hole_obj['archipack_hole'] = True + hole_obj.parent = o + hole_obj.matrix_world = o.matrix_world.copy() + MaterialUtils.add_wall2_materials(hole_obj) + + hole = self.hole + center, origin, size, radius = self.get_radius() + + if self.out_frame is False: + x0 = 0 + else: + x0 = min(self.frame_x - 0.001, self.out_frame_y + self.out_frame_offset) + + if self.out_tablet_enable: + x0 -= min(self.frame_x - 0.001, self.out_tablet_z) + shape_z = [0, x0] + + verts = hole.vertices(self.curve_steps, Vector((0, self.altitude, 0)), center, origin, size, radius, + self.angle_y, 0, shape_z=shape_z, path_type=self.shape) + + faces = hole.faces(self.curve_steps, path_type=self.shape) + + matids = hole.mat(self.curve_steps, 2, 2, path_type=self.shape) + + uvs = hole.uv(self.curve_steps, center, origin, size, radius, + self.angle_y, 0, 0, self.frame_x, path_type=self.shape) + + bmed.buildmesh(context, hole_obj, verts, faces, matids=matids, uvs=uvs) + return hole_obj + + def robust_hole(self, context, tM): + hole = self.hole + center, origin, size, radius = self.get_radius() + + if self.out_frame is False: + x0 = 0 + else: + x0 = min(self.frame_x - 0.001, self.out_frame_y + self.out_frame_offset) + + if self.out_tablet_enable: + x0 -= min(self.frame_x - 0.001, self.out_tablet_z) + shape_z = [0, x0] + + m = bpy.data.meshes.new("hole") + o = bpy.data.objects.new("hole", m) + o['archipack_robusthole'] = True + context.scene.objects.link(o) + verts = hole.vertices(self.curve_steps, Vector((0, self.altitude, 0)), center, origin, size, radius, + self.angle_y, 0, shape_z=shape_z, path_type=self.shape) + + verts = [tM * Vector(v) for v in verts] + + faces = hole.faces(self.curve_steps, path_type=self.shape) + + matids = hole.mat(self.curve_steps, 2, 2, path_type=self.shape) + + uvs = hole.uv(self.curve_steps, center, origin, size, radius, + self.angle_y, 0, 0, self.frame_x, path_type=self.shape) + + bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs) + MaterialUtils.add_wall2_materials(o) + o.select = True + context.scene.objects.active = o + return o + + +class ARCHIPACK_PT_window(Panel): + bl_idname = "ARCHIPACK_PT_window" + bl_label = "Window" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + # bl_context = 'object' + bl_category = 'ArchiPack' + + # layout related + display_detail = BoolProperty( + default=False + ) + display_panels = BoolProperty( + default=True + ) + + @classmethod + def poll(cls, context): + return archipack_window.filter(context.active_object) + + def draw(self, context): + o = context.active_object + prop = archipack_window.datablock(o) + if prop is None: + return + layout = self.layout + layout.operator('archipack.window_manipulate', icon='HAND') + row = layout.row(align=True) + row.operator('archipack.window', text="Refresh", icon='FILE_REFRESH').mode = 'REFRESH' + if o.data.users > 1: + row.operator('archipack.window', text="Make unique", icon='UNLINKED').mode = 'UNIQUE' + row.operator('archipack.window', text="Delete", icon='ERROR').mode = 'DELETE' + box = layout.box() + # box.label(text="Styles") + row = box.row(align=True) + row.operator("archipack.window_preset_menu", text=bpy.types.ARCHIPACK_OT_window_preset_menu.bl_label) + row.operator("archipack.window_preset", text="", icon='ZOOMIN') + row.operator("archipack.window_preset", text="", icon='ZOOMOUT').remove_active = True + box = layout.box() + box.prop(prop, 'window_type') + box.prop(prop, 'x') + box.prop(prop, 'y') + if prop.window_shape != 'CIRCLE': + box.prop(prop, 'z') + if prop.warning: + box.label(text="Insufficient height", icon='ERROR') + box.prop(prop, 'altitude') + box.prop(prop, 'offset') + + if prop.window_type == 'FLAT': + box = layout.box() + box.prop(prop, 'window_shape') + if prop.window_shape in ['ROUND', 'CIRCLE', 'ELLIPSIS']: + box.prop(prop, 'curve_steps') + if prop.window_shape in ['ROUND']: + box.prop(prop, 'radius') + elif prop.window_shape == 'ELLIPSIS': + box.prop(prop, 'elipsis_b') + elif prop.window_shape == 'QUADRI': + box.prop(prop, 'angle_y') + + row = layout.row(align=True) + if prop.display_detail: + row.prop(prop, "display_detail", icon="TRIA_DOWN", icon_only=True, text="Components", emboss=False) + else: + row.prop(prop, "display_detail", icon="TRIA_RIGHT", icon_only=True, text="Components", emboss=False) + + if prop.display_detail: + box = layout.box() + box.label("Frame") + box.prop(prop, 'frame_x') + box.prop(prop, 'frame_y') + if prop.window_shape != 'CIRCLE': + box = layout.box() + row = box.row(align=True) + row.prop(prop, 'handle_enable') + if prop.handle_enable: + box.prop(prop, 'handle_altitude') + box = layout.box() + row = box.row(align=True) + row.prop(prop, 'out_frame') + if prop.out_frame: + box.prop(prop, 'out_frame_x') + box.prop(prop, 'out_frame_y2') + box.prop(prop, 'out_frame_y') + box.prop(prop, 'out_frame_offset') + if prop.window_shape != 'CIRCLE': + box = layout.box() + row = box.row(align=True) + row.prop(prop, 'out_tablet_enable') + if prop.out_tablet_enable: + box.prop(prop, 'out_tablet_x') + box.prop(prop, 'out_tablet_y') + box.prop(prop, 'out_tablet_z') + box = layout.box() + row = box.row(align=True) + row.prop(prop, 'in_tablet_enable') + if prop.in_tablet_enable: + box.prop(prop, 'in_tablet_x') + box.prop(prop, 'in_tablet_y') + box.prop(prop, 'in_tablet_z') + box = layout.box() + row = box.row(align=True) + row.prop(prop, 'blind_enable') + if prop.blind_enable: + box.prop(prop, 'blind_open') + box.prop(prop, 'blind_y') + box.prop(prop, 'blind_z') + if prop.window_shape != 'CIRCLE': + row = layout.row() + if prop.display_panels: + row.prop(prop, "display_panels", icon="TRIA_DOWN", icon_only=True, text="Rows", emboss=False) + else: + row.prop(prop, "display_panels", icon="TRIA_RIGHT", icon_only=True, text="Rows", emboss=False) + + if prop.display_panels: + if prop.window_type != 'RAIL': + row = layout.row() + row.prop(prop, 'n_rows') + last_row = prop.n_rows - 1 + for i, row in enumerate(prop.rows): + box = layout.box() + box.label(text="Row " + str(i + 1)) + row.draw(box, context, i == last_row) + else: + box = layout.box() + row = prop.rows[0] + row.draw(box, context, True) + + row = layout.row(align=True) + if prop.display_materials: + row.prop(prop, "display_materials", icon="TRIA_DOWN", icon_only=True, text="Materials", emboss=False) + else: + row.prop(prop, "display_materials", icon="TRIA_RIGHT", icon_only=True, text="Materials", emboss=False) + if prop.display_materials: + box = layout.box() + box.label("Hole") + box.prop(prop, 'hole_inside_mat') + box.prop(prop, 'hole_outside_mat') + + +class ARCHIPACK_PT_window_panel(Panel): + bl_idname = "ARCHIPACK_PT_window_panel" + bl_label = "Window panel" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'ArchiPack' + + @classmethod + def poll(cls, context): + return archipack_window_panel.filter(context.active_object) + + def draw(self, context): + layout = self.layout + layout.operator("archipack.select_parent") + + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_window(ArchipackCreateTool, Operator): + bl_idname = "archipack.window" + bl_label = "Window" + bl_description = "Window" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + x = FloatProperty( + name='width', + min=0.1, max=10000, + default=2.0, precision=2, + description='Width' + ) + y = FloatProperty( + name='depth', + min=0.1, max=10000, + default=0.20, precision=2, + description='Depth' + ) + z = FloatProperty( + name='height', + min=0.1, max=10000, + default=1.2, precision=2, + description='height' + ) + altitude = FloatProperty( + name='altitude', + min=0.0, max=10000, + default=1.0, precision=2, + description='altitude' + ) + mode = EnumProperty( + items=( + ('CREATE', 'Create', '', 0), + ('DELETE', 'Delete', '', 1), + ('REFRESH', 'Refresh', '', 2), + ('UNIQUE', 'Make unique', '', 3), + ), + default='CREATE' + ) + # auto_manipulate = BoolProperty(default=True) + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def create(self, context): + m = bpy.data.meshes.new("Window") + o = bpy.data.objects.new("Window", m) + d = m.archipack_window.add() + d.x = self.x + d.y = self.y + d.z = self.z + d.altitude = self.altitude + context.scene.objects.link(o) + o.select = True + context.scene.objects.active = o + self.load_preset(d) + self.add_material(o) + # select frame + o.select = True + context.scene.objects.active = o + return o + + def delete(self, context): + o = context.active_object + if archipack_window.filter(o): + bpy.ops.archipack.disable_manipulate() + for child in o.children: + if 'archipack_hole' in child: + context.scene.objects.unlink(child) + bpy.data.objects.remove(child, do_unlink=True) + elif child.data is not None and 'archipack_window_panel' in child.data: + for handle in child.children: + if 'archipack_handle' in handle: + context.scene.objects.unlink(handle) + bpy.data.objects.remove(handle, do_unlink=True) + context.scene.objects.unlink(child) + bpy.data.objects.remove(child, do_unlink=True) + context.scene.objects.unlink(o) + bpy.data.objects.remove(o, do_unlink=True) + + def update(self, context): + o = context.active_object + d = archipack_window.datablock(o) + if d is not None: + d.update(context) + bpy.ops.object.select_linked(type='OBDATA') + for linked in context.selected_objects: + if linked != o: + archipack_window.datablock(linked).update(context) + bpy.ops.object.select_all(action="DESELECT") + o.select = True + context.scene.objects.active = o + + def unique(self, context): + act = context.active_object + sel = [o for o in context.selected_objects] + bpy.ops.object.select_all(action="DESELECT") + for o in sel: + if archipack_window.filter(o): + o.select = True + for child in o.children: + if 'archipack_hole' in child or ( + child.data is not None and + 'archipack_window_panel' in child.data): + child.hide_select = False + child.select = True + if len(context.selected_objects) > 0: + bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, + obdata=True, material=False, texture=False, animation=False) + for child in context.selected_objects: + if 'archipack_hole' in child: + child.hide_select = True + bpy.ops.object.select_all(action="DESELECT") + context.scene.objects.active = act + for o in sel: + o.select = True + + # ----------------------------------------------------- + # Execute + # ----------------------------------------------------- + def execute(self, context): + if context.mode == "OBJECT": + if self.mode == 'CREATE': + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.location = bpy.context.scene.cursor_location + o.select = True + context.scene.objects.active = o + self.manipulate() + elif self.mode == 'DELETE': + self.delete(context) + elif self.mode == 'REFRESH': + self.update(context) + elif self.mode == 'UNIQUE': + self.unique(context) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator): + bl_idname = "archipack.window_draw" + bl_label = "Draw Windows" + bl_description = "Draw Windows over walls" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + filepath = StringProperty(default="") + feedback = None + stack = [] + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def draw_callback(self, _self, context): + self.feedback.draw(context) + + def add_object(self, context, event): + o = context.active_object + bpy.ops.object.select_all(action="DESELECT") + + if archipack_window.filter(o): + + o.select = True + context.scene.objects.active = o + + if event.shift: + bpy.ops.archipack.window(mode="UNIQUE") + + new_w = o.copy() + new_w.data = o.data + context.scene.objects.link(new_w) + + o = new_w + o.select = True + context.scene.objects.active = o + + # synch subs from parent instance + bpy.ops.archipack.window(mode="REFRESH") + + else: + bpy.ops.archipack.window(auto_manipulate=False, filepath=self.filepath) + o = context.active_object + + bpy.ops.archipack.generate_hole('INVOKE_DEFAULT') + o.select = True + context.scene.objects.active = o + + def modal(self, context, event): + + context.area.tag_redraw() + o = context.active_object + d = archipack_window.datablock(o) + hole = None + if d is not None: + hole = d.find_hole(o) + + # hide hole from raycast + if hole is not None: + o.hide = True + hole.hide = True + + res, tM, wall, y = self.mouse_hover_wall(context, event) + + if hole is not None: + o.hide = False + hole.hide = False + + if res and d is not None: + o.matrix_world = tM + if d.y != wall.data.archipack_wall2[0].width: + d.y = wall.data.archipack_wall2[0].width + + if event.value == 'PRESS': + if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}: + if wall is not None: + context.scene.objects.active = wall + wall.select = True + if bpy.ops.archipack.single_boolean.poll(): + bpy.ops.archipack.single_boolean() + wall.select = False + # o must be a window here + if d is not None: + context.scene.objects.active = o + self.stack.append(o) + self.add_object(context, event) + context.active_object.matrix_world = tM + return {'RUNNING_MODAL'} + # prevent selection of other object + if event.type in {'RIGHTMOUSE'}: + return {'RUNNING_MODAL'} + + if self.keymap.check(event, self.keymap.undo) or ( + event.type in {'BACK_SPACE'} and event.value == 'RELEASE' + ): + if len(self.stack) > 0: + last = self.stack.pop() + context.scene.objects.active = last + bpy.ops.archipack.window(mode="DELETE") + context.scene.objects.active = o + return {'RUNNING_MODAL'} + + if event.value == 'RELEASE': + + if event.type in {'ESC', 'RIGHTMOUSE'}: + bpy.ops.archipack.window(mode='DELETE') + self.feedback.disable() + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + return {'FINISHED'} + + return {'PASS_THROUGH'} + + def invoke(self, context, event): + + if context.mode == "OBJECT": + o = None + self.stack = [] + self.keymap = Keymaps(context) + # exit manipulate_mode if any + bpy.ops.archipack.disable_manipulate() + # invoke with shift pressed will use current object as basis for linked copy + if self.filepath == '' and archipack_window.filter(context.active_object): + o = context.active_object + context.scene.objects.active = None + bpy.ops.object.select_all(action="DESELECT") + if o is not None: + o.select = True + context.scene.objects.active = o + self.add_object(context, event) + self.feedback = FeedbackPanel() + self.feedback.instructions(context, "Draw a window", "Click & Drag over a wall", [ + ('LEFTCLICK, RET, SPACE, ENTER', 'Create a window'), + ('BACKSPACE, CTRL+Z', 'undo last'), + ('SHIFT', 'Make independant copy'), + ('RIGHTCLICK or ESC', 'exit') + ]) + self.feedback.enable() + args = (self, context) + + self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback, args, 'WINDOW', 'POST_PIXEL') + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_window_panel(Operator): + bl_idname = "archipack.window_panel" + bl_label = "Window panel" + bl_description = "Window panel" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + center = FloatVectorProperty( + subtype='XYZ' + ) + origin = FloatVectorProperty( + subtype='XYZ' + ) + size = FloatVectorProperty( + subtype='XYZ' + ) + radius = FloatVectorProperty( + subtype='XYZ' + ) + angle_y = FloatProperty( + name='angle', + unit='ROTATION', + subtype='ANGLE', + min=-1.5, max=1.5, + default=0, precision=2, + description='angle' + ) + frame_y = FloatProperty( + name='Depth', + min=0, max=100, + default=0.06, precision=2, + description='frame depth' + ) + frame_x = FloatProperty( + name='Width', + min=0, max=100, + default=0.06, precision=2, + description='frame width' + ) + curve_steps = IntProperty( + name="curve steps", + min=1, + max=128, + default=16 + ) + shape = EnumProperty( + name='Shape', + items=( + ('RECTANGLE', 'Rectangle', '', 0), + ('ROUND', 'Top Round', '', 1), + ('ELLIPSIS', 'Top Elliptic', '', 2), + ('QUADRI', 'Top oblique', '', 3), + ('CIRCLE', 'Full circle', '', 4) + ), + default='RECTANGLE' + ) + pivot = FloatProperty( + name='pivot', + min=-1, max=1, + default=-1, precision=2, + description='pivot' + ) + side_material = IntProperty( + name="side material", + min=0, + max=2, + default=0 + ) + handle = EnumProperty( + name='Handle', + items=( + ('NONE', 'No handle', '', 0), + ('INSIDE', 'Inside', '', 1), + ('BOTH', 'Inside and outside', '', 2) + ), + default='NONE' + ) + handle_model = IntProperty( + name="handle model", + default=1, + min=1, + max=2 + ) + handle_altitude = FloatProperty( + name='handle altitude', + min=0, max=1000, + default=0.2, precision=2, + description='handle altitude' + ) + fixed = BoolProperty( + name="Fixed", + default=False + ) + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def create(self, context): + m = bpy.data.meshes.new("Window Panel") + o = bpy.data.objects.new("Window Panel", m) + d = m.archipack_window_panel.add() + d.center = self.center + d.origin = self.origin + d.size = self.size + d.radius = self.radius + d.frame_y = self.frame_y + d.frame_x = self.frame_x + d.curve_steps = self.curve_steps + d.shape = self.shape + d.fixed = self.fixed + d.pivot = self.pivot + d.angle_y = self.angle_y + d.side_material = self.side_material + d.handle = self.handle + d.handle_model = self.handle_model + d.handle_altitude = self.handle_altitude + context.scene.objects.link(o) + o.select = True + context.scene.objects.active = o + o.lock_location[0] = True + o.lock_location[1] = True + o.lock_location[2] = True + o.lock_rotation[1] = True + o.lock_scale[0] = True + o.lock_scale[1] = True + o.lock_scale[2] = True + d.update(context) + MaterialUtils.add_window_materials(o) + return o + + def execute(self, context): + if context.mode == "OBJECT": + o = self.create(context) + o.select = True + context.scene.objects.active = o + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + +# ------------------------------------------------------------------ +# Define operator class to manipulate object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_window_manipulate(Operator): + bl_idname = "archipack.window_manipulate" + bl_label = "Manipulate" + bl_description = "Manipulate" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_window.filter(context.active_object) + + def invoke(self, context, event): + d = archipack_window.datablock(context.active_object) + d.manipulable_invoke(context) + return {'FINISHED'} + +# ------------------------------------------------------------------ +# Define operator class to load / save presets +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_window_preset_menu(PresetMenuOperator, Operator): + bl_idname = "archipack.window_preset_menu" + bl_label = "Window Presets" + preset_subdir = "archipack_window" + + +class ARCHIPACK_OT_window_preset(ArchipackPreset, Operator): + """Add a Window Preset""" + bl_idname = "archipack.window_preset" + bl_label = "Add Window Preset" + preset_menu = "ARCHIPACK_OT_window_preset_menu" + + @property + def blacklist(self): + # 'x', 'y', 'z', 'altitude', 'window_shape' + return ['manipulators'] + + +def register(): + bpy.utils.register_class(archipack_window_panelrow) + bpy.utils.register_class(archipack_window_panel) + Mesh.archipack_window_panel = CollectionProperty(type=archipack_window_panel) + bpy.utils.register_class(ARCHIPACK_PT_window_panel) + bpy.utils.register_class(ARCHIPACK_OT_window_panel) + bpy.utils.register_class(archipack_window) + Mesh.archipack_window = CollectionProperty(type=archipack_window) + bpy.utils.register_class(ARCHIPACK_OT_window_preset_menu) + bpy.utils.register_class(ARCHIPACK_PT_window) + bpy.utils.register_class(ARCHIPACK_OT_window) + bpy.utils.register_class(ARCHIPACK_OT_window_preset) + bpy.utils.register_class(ARCHIPACK_OT_window_draw) + bpy.utils.register_class(ARCHIPACK_OT_window_manipulate) + + +def unregister(): + bpy.utils.unregister_class(archipack_window_panelrow) + bpy.utils.unregister_class(archipack_window_panel) + bpy.utils.unregister_class(ARCHIPACK_PT_window_panel) + del Mesh.archipack_window_panel + bpy.utils.unregister_class(ARCHIPACK_OT_window_panel) + bpy.utils.unregister_class(archipack_window) + del Mesh.archipack_window + bpy.utils.unregister_class(ARCHIPACK_OT_window_preset_menu) + bpy.utils.unregister_class(ARCHIPACK_PT_window) + bpy.utils.unregister_class(ARCHIPACK_OT_window) + bpy.utils.unregister_class(ARCHIPACK_OT_window_preset) + bpy.utils.unregister_class(ARCHIPACK_OT_window_draw) + bpy.utils.unregister_class(ARCHIPACK_OT_window_manipulate) diff --git a/archipack/bitarray.py b/archipack/bitarray.py new file mode 100644 index 000000000..cf7126107 --- /dev/null +++ b/archipack/bitarray.py @@ -0,0 +1,97 @@ + +import array + +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- + + +class BitArray(): + + def __init__(self, bitSize, fill=0): + self.size = bitSize + intSize = bitSize >> 5 + if (bitSize & 31): + intSize += 1 + if fill == 1: + fill = 4294967295 + else: + fill = 0 + self.bitArray = array.array('I') + self.bitArray.extend((fill,) * intSize) + + def __str__(self): + return str(self.list) + + def bit_location(self, bit_num): + return bit_num >> 5, bit_num & 31 + + def test(self, bit_num): + record, offset = self.bit_location(bit_num) + mask = 1 << offset + return(self.bitArray[record] & mask) + + def set(self, bit_num): + record, offset = self.bit_location(bit_num) + mask = 1 << offset + self.bitArray[record] |= mask + + def clear(self, bit_num): + record, offset = self.bit_location(bit_num) + mask = ~(1 << offset) + self.bitArray[record] &= mask + + def toggle(self, bit_num): + record, offset = self.bit_location(bit_num) + mask = 1 << offset + self.bitArray[record] ^= mask + + @property + def len(self): + return len(self.bitArray) + + @property + def copy(self): + copy = BitArray(self.size) + for i in range(self.len): + copy.bitArray[i] = self.bitArray[i] + return copy + + @property + def list(self): + return [x for x in range(self.size) if self.test(x) > 0] + + def none(self): + for i in range(self.len): + self.bitArray[i] = 0 + + def reverse(self): + for i in range(self.len): + self.bitArray[i] = 4294967295 ^ self.bitArray[i] + + def all(self): + for i in range(self.len): + self.bitArray[i] = 4294967295 diff --git a/archipack/bmesh_utils.py b/archipack/bmesh_utils.py new file mode 100644 index 000000000..b49f46831 --- /dev/null +++ b/archipack/bmesh_utils.py @@ -0,0 +1,249 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +import bpy +import bmesh + + +class BmeshEdit(): + @staticmethod + def _start(context, o): + """ + private, start bmesh editing of active object + """ + o.select = True + context.scene.objects.active = o + bpy.ops.object.mode_set(mode='EDIT') + bm = bmesh.from_edit_mesh(o.data) + bm.verts.ensure_lookup_table() + bm.faces.ensure_lookup_table() + return bm + + @staticmethod + def _end(bm, o): + """ + private, end bmesh editing of active object + """ + bm.normal_update() + bmesh.update_edit_mesh(o.data, True) + bpy.ops.object.mode_set(mode='OBJECT') + bm.free() + + @staticmethod + def _matids(bm, matids): + for i, matid in enumerate(matids): + bm.faces[i].material_index = matid + + @staticmethod + def _uvs(bm, uvs): + layer = bm.loops.layers.uv.verify() + l_i = len(uvs) + for i, face in enumerate(bm.faces): + if i > l_i: + raise RuntimeError("Missing uvs for face {}".format(i)) + l_j = len(uvs[i]) + for j, loop in enumerate(face.loops): + if j > l_j: + raise RuntimeError("Missing uv {} for face {}".format(j, i)) + loop[layer].uv = uvs[i][j] + + @staticmethod + def _verts(bm, verts): + for i, v in enumerate(verts): + bm.verts[i].co = v + + @staticmethod + def buildmesh(context, o, verts, faces, matids=None, uvs=None, weld=False, clean=False, auto_smooth=True): + bm = BmeshEdit._start(context, o) + bm.clear() + for v in verts: + bm.verts.new(v) + bm.verts.ensure_lookup_table() + for f in faces: + bm.faces.new([bm.verts[i] for i in f]) + bm.faces.ensure_lookup_table() + if matids is not None: + BmeshEdit._matids(bm, matids) + if uvs is not None: + BmeshEdit._uvs(bm, uvs) + if weld: + bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001) + BmeshEdit._end(bm, o) + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + if auto_smooth: + bpy.ops.mesh.faces_shade_smooth() + o.data.use_auto_smooth = True + else: + bpy.ops.mesh.faces_shade_flat() + if clean: + bpy.ops.mesh.delete_loose() + bpy.ops.object.mode_set(mode='OBJECT') + + @staticmethod + def addmesh(context, o, verts, faces, matids=None, uvs=None, weld=False, clean=False, auto_smooth=True): + bm = BmeshEdit._start(context, o) + nv = len(bm.verts) + nf = len(bm.faces) + + for v in verts: + bm.verts.new(v) + + bm.verts.ensure_lookup_table() + + for f in faces: + bm.faces.new([bm.verts[nv + i] for i in f]) + + bm.faces.ensure_lookup_table() + + if matids is not None: + for i, matid in enumerate(matids): + bm.faces[nf + i].material_index = matid + + if uvs is not None: + layer = bm.loops.layers.uv.verify() + for i, face in enumerate(bm.faces[nf:]): + for j, loop in enumerate(face.loops): + loop[layer].uv = uvs[i][j] + + if weld: + bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001) + BmeshEdit._end(bm, o) + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + if auto_smooth: + bpy.ops.mesh.faces_shade_smooth() + o.data.use_auto_smooth = True + else: + bpy.ops.mesh.faces_shade_flat() + if clean: + bpy.ops.mesh.delete_loose() + bpy.ops.object.mode_set(mode='OBJECT') + + @staticmethod + def bevel(context, o, + offset, + offset_type=0, + segments=1, + profile=0.5, + vertex_only=False, + clamp_overlap=True, + material=-1, + use_selection=True): + """ + /* Bevel offset_type slot values */ + enum { + BEVEL_AMT_OFFSET, + BEVEL_AMT_WIDTH, + BEVEL_AMT_DEPTH, + BEVEL_AMT_PERCENT + }; + """ + bm = bmesh.new() + bm.from_mesh(o.data) + bm.verts.ensure_lookup_table() + if use_selection: + geom = [v for v in bm.verts if v.select] + geom.extend([ed for ed in bm.edges if ed.select]) + else: + geom = bm.verts[:] + geom.extend(bm.edges[:]) + + bmesh.ops.bevel(bm, + geom=geom, + offset=offset, + offset_type=offset_type, + segments=segments, + profile=profile, + vertex_only=vertex_only, + clamp_overlap=clamp_overlap, + material=material) + + bm.to_mesh(o.data) + bm.free() + + @staticmethod + def bissect(context, o, + plane_co, + plane_no, + dist=0.001, + use_snap_center=False, + clear_outer=True, + clear_inner=False + ): + + bm = bmesh.new() + bm.from_mesh(o.data) + bm.verts.ensure_lookup_table() + geom = bm.verts[:] + geom.extend(bm.edges[:]) + geom.extend(bm.faces[:]) + + bmesh.ops.bisect_plane(bm, + geom=geom, + dist=dist, + plane_co=plane_co, + plane_no=plane_no, + use_snap_center=False, + clear_outer=clear_outer, + clear_inner=clear_inner + ) + + bm.to_mesh(o.data) + bm.free() + + @staticmethod + def solidify(context, o, amt, floor_bottom=False, altitude=0): + bm = bmesh.new() + bm.from_mesh(o.data) + bm.verts.ensure_lookup_table() + geom = bm.faces[:] + bmesh.ops.solidify(bm, geom=geom, thickness=amt) + if floor_bottom: + for v in bm.verts: + if not v.select: + v.co.z = altitude + bm.to_mesh(o.data) + bm.free() + + @staticmethod + def verts(context, o, verts): + """ + update vertex position of active object + """ + bm = BmeshEdit._start(context, o) + BmeshEdit._verts(bm, verts) + BmeshEdit._end(bm, o) + + @staticmethod + def aspect(context, o, matids, uvs): + """ + update material id and uvmap of active object + """ + bm = BmeshEdit._start(context, o) + BmeshEdit._matids(bm, matids) + BmeshEdit._uvs(bm, uvs) + BmeshEdit._end(bm, o) diff --git a/archipack/icons/archipack.png b/archipack/icons/archipack.png new file mode 100644 index 0000000000000000000000000000000000000000..92503c824dbba4bca5148055310822c93ef66fec GIT binary patch literal 1364 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}Y)RhkE)4%caKYZ?lNlHo zI14-?iy0WWg+Z8+Vb&Z81_lQ95>H=O_J?fZeBvgxua{~tFt9xIba4!cIQ({+Z$@yW z$npB`wO>^A^)rJmhxkY@I{N6k)9dRBVeIU44seM1hCOnM*&)cf@Pg}fzI80$yPB*g zK5$~&RiNm!bc^1qM#<G7Ep-__(Q}sN{)+ollzvfdvftXMbQy_b%>BprzQ6n1{(Jqo zozMC2znT5_zHiOa+ENE*DF)VphBxZrbM0!YcI@8$d;5N$)Ab)B6TRLZ*!K2U?(J=^ z)7r!omM>pEF{tk2<dZ2D#m{*7<!n~SxW!Iw{PykJL22{6GvPM>tkZ69+f)3!?@NV^ z(1eEt5z8*W<Pv^u_rgJG;*YP-&dwH^urtQ*-1+nMf)#qxC)?WE7F}NEduh{}ERH>K z>-SWAOiGnmt+J5e)cJX~r<YxR>2|zu%HekY%NH*;X3ok!WpllB=8PE&cJH43{QUgI znNmxGI%m$5Ja+uJrU=)IUFS9?AKy{_Ud|zg;nTObw<D&tu6tclQW6j!zyH~m^Upu0 z7)kEhwd=$0zjJJ>#SR<nsQ#{(e{avjzrVlt^!83Xo92DMLuq2i*Kgmdjta@zzrVNl z_iyKRzS`^8t}V;Exv4e7#7jZo!1K?Ek=NW0ZoaAWvSill>+3fcKlck-DY7vlXJgBf zCm$al|MK;#p_SFC!|nXpr{e{;J{9afn&x|XON&6u;lx|FZr#{dYke_8<n*Qv7bO=Z z!9^D}R;_wu!T0#_<CwiwQyUu@g*sW>`{h(cxD=m0+UCR|;;K0P^v5qRgY{x|Y<Tl; z<=&1b`)YsJvA=r$?AeCg*)?|lD?^k7I0X8S-`tqYuB)rNW8LXSW_AOf=6UnvVy>&s z^x2etUXJIm!(|hZr{}rEofMY3a2&k0HhTKQtgRt#i!W{xX!`lHQcO&2L$37gZJHun zdn!IQsr%1cpuzRAVvdj6;S(n~e0_cASQIvW`&QN>aOLXNhaW2nV#RN73lw2h6#E!b z`c!no8OGS_r+s~WE&816>+LUJzPx4276GnSj`qVl)}4OyMyI&A_{;b2#ZOKM_Q~7( zWoK)zdmZ+2U;TeOyZQai?EGQ=dP`j{Z|T3^^8Vi5)30~=DF{5cx;nh4ug}QF#>d&2 z`C-9~1Ot!cWaYDG&%U{}wL5sZ-^RknZl|6aty;C{wbk`?v5)`!{QTwX*U--nUOx|B zUZ0>a{dDijRZ`)hQv)<y?B=IuWpQ0L`B=4Aq}!FNmC4xHn5Ef~snNmonrOq`>hEFB zfwP&P7X7@;<KySIZ{_OMhf8CvtgSm;#BLwDaz&&q@xkYx9Bj=;k_@G!r2|7n6<tJQ zu9vd0vri0qWa0O<TGS`d?wo_!?6-4g%=oc!=T1w7mIrrs7K?DPe){>R>u6izf>p0J zsZ0*iOI~2K{<W18hn#Jd37<=Nu>-@WYipx(?(O+GJ;mr}@Wu!h1p$r+yWSaX;MzL1 z@af*f!)!XIS6zDh{kNo))GTimp?#_a8?!jH_C9=idb)k=&Z5@s`S-2l`j>mftDeqI zj(hF0RDQ*(uKVxV6aN1Cs?)eyUdUm7kc-fROG~{~eJ^kE>KAuPn5DKdgzK=us+;Rp zX*oMP|D3Gu&&Ob2!*}kvml5ZMt5;hSB`o@yGiRwx_AH5Y4;1maoRX8XW=sA5x?J7r z^{&F}nAI5d;`jM%j8K{C_3-;|&8c2nwrvah_nC=1KKO{67SrReudi>dI{#z4XzBAT hr_ZzhE`P&6x3}Wp>ebRO7#J8BJYD@<);T3K0RV{^mn;AP literal 0 HcmV?d00001 diff --git a/archipack/icons/detect.png b/archipack/icons/detect.png new file mode 100644 index 0000000000000000000000000000000000000000..9c10f604ff74be249665798ab03ec4ccfdd01fb1 GIT binary patch literal 281 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}Y)RhkE(~TMXi@j;5d#AQ zXMsm#F#`k3dk|(+zkWxafq{X&#M9T6{UMVy535nYy1h3T7#Nm$x;TbJ9DY0PAlDHC z4wvJq_f7u(fBY->+a(>_tx{W39rK%JIMzD1ELo@!5*pJg%A{m#Utt;Lz@5Rko?Bw( zS)L7t+4@BU9OULUFl?H*oxffE!KU!g30u}{nO#bo_vrS#T|1j1e@CBmJwI_T6Z4xI z=KdgshB^5MirL>XFK&AKh12CGpZ^1)jiTGM-&{Ri_T{~W)(yYCjiTFr+h1UcIuOrU eU>uve-#Wb3{aH)w>SzWA1_n=8KbLh*2~7Z;&uF#) literal 0 HcmV?d00001 diff --git a/archipack/icons/door.png b/archipack/icons/door.png new file mode 100644 index 0000000000000000000000000000000000000000..dc975d4d0d904cd54f0e3d76176143dcaf726555 GIT binary patch literal 414 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}Y)RhkE)4%caKYZ?lNlHo zI14-?iy0WWg+Z8+Vb&Z81_lQ95>H=O_J^zj+#=fSMb&c{7#L(TLn2C?^K)}k^GX;% zz_}<ju_QG`p**uBL&4qCHy}kXm7Rfsk;&7=F(l&f+v^+onhXTk9%x@Y!pGNR*RQ`& zS=mM)FV#gYzUhOM-!Wfyb~d4Uc16d=#UdA$PA_@bP?!5+qVN%R4}BKNw!^-1+O8Gs zwY};MOb&bx0-bgyvOEZTE^u*Dhl=3Z=!UC1j(Kgg-g)7!R`{uNcWlq^4w}91dloZa z;)>p6hi442bL3f?=gIQisEm7j%djh>FW7+ND%)?#1fPfo)`$kd15+pcH9r%@`FiQ~ zsYL;I=kp{)eO3F<wyfbsZ~~)P0%JY<i)l$AvXcbn_X;sw`IO`(bNjif=O(YqCHKqt z8@z7@D}}AvZZ9JmIW1SZ=mE<Ewg)jHt>+n!_~^Yj_nB$ep(%$VWHb+fg2~g>&t;uc GLK6Vj0hKWT literal 0 HcmV?d00001 diff --git a/archipack/icons/fence.png b/archipack/icons/fence.png new file mode 100644 index 0000000000000000000000000000000000000000..f32dcc7eb4a2bb12c4c6029d626cd2326288a849 GIT binary patch literal 1779 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 literal 0 HcmV?d00001 diff --git a/archipack/icons/floor.png b/archipack/icons/floor.png new file mode 100644 index 0000000000000000000000000000000000000000..1590c335ae63191921ed621d2f8844ded23c8230 GIT binary patch literal 1457 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}Y)RhkE)4%caKYZ?lNlHo zI14-?iy0WWg+Z8+Vb&Z81_lQ95>H=O_J?fZeDWq+jr0{57+CE*T^vIq4!@li?=v}E z;@JH4_p>yY&bySAyX~4A^ED>NN7H(8nk5<;KJq_w<1sY8#FA^IXgurWr4*Ij0wO)# z4-ci}ajSW+%{!LTBA~QnlHTFcWe3t$TroJ_#`@;M)~Ee<E6S2P&&68IiT0_Oq;F}i zzW=-J^PlH_Gn|gP#;s%8F!eus1ltDw1O5z$816AHQ29CkKsO@`LyfLy&_uVzh0mWp z{ktpaXjdUaKJ$cm;y+9oY$vJkYESh#zAg8*T}Dn$O~Uruk#TY7Cae2jN-??{sx_6d z*KM=u&XsG|zMXk6jNvWAv6RSv8<s42GUM{gIa)e8PYyP-i|^XKJ2E!bSCK_mjC*_T z{e25AW-Qwp_12uBh+)CTjfI{G%nfP`<*AYXzMPn-oY#K1(0%>&&D*z6-@AA3o|2bB ztpXxW0n0DzuJiWtdQr6VUG~;n6;)Ncq#It&7n#KMz?LC6Gc$8u@N&OcL8l`<Zpy#c z$0~cKrKJUN99*_c?O2lGv}x1y4&9aG_Y7KoIdJt=y^zpQ+Y1>cn>XofXS(oD*U;8h zmO+Hy_e*!s$~POL*2Z%wMuf+oFR(bnEv|25Z$Dpca%ZDMgWddoU0q!frw?y#ZkCae zS#tk<dRdtnHy787@4sKZex3aJ<Kvi}MXL9;G(1*>tomKG_n$_?bOr^verEpI-)s&2 zs-9h|ufDQwZEdx_XIsK?pPyGzu3uS%>*1%Tr~5_sR)5cX^7Lt9QWDc76-8B5)=n2D z<Kp58s+>+76*h7#%?FR#Mn*<T$ji^~bW!?lWMm}8)aX#X>GO5Q6RxhVpTky{x)&B2 zwzaiQm^!uf>+9?GjO+~s7Cf`hvduo*rW?I&!QQ>HlT;QiS-}y#J<qi!?U|tk!<`+4 z%xs5Wg>1k5x5z@qj&p(dfz>D5pU&`F`fKg7Wo=WZP8D&wkYe=l+uPf7=FUwmEHrEt zh}oYf+pMhI<F<Iq%^VS@4VyMiQmK4*$MV(Js#jmD=2;fEsqC!${LHm}%eHNkRE{4z zma+YIGXon#ifz;rzQ}2dpFVxs+1AEZwKwk8+p<?*tN3_$3Q9_*FgG`wNbu<B>ONf? zz4*<mSC6i)4%ci+C@waxuBy6m_pWdMap$>y$K%#dpF4N%p1QwQo|_ggPS(@a)&2D0 zx}R?uKSROJ81ubx@5L7{UMxC!^5lA!=7U#OuG9<(2}vj`6YF$gs@pHm)ykB%Su(4X z>(;GX1&@w!GC0J?%h%Y+YiVg+xOtP4VaD8<jh9~@nPr;&pv0=VonL<1^XJdMPs}OX zeYev?<<pP5dAGK3GHl^$eWX3tPki0lwNGDOUY=<nq0zG7{`=>5?#QSwTb5xWCEovi z{r&W}Z&h|WOtPD=pJpPJ?=|TO>xWbK_Eyh-WjldEW@@TsP-y7UzrVk4-oJl7Uwd=* z^>wMSwYJR%6?DZ;3x8O*PH$3*j@aVUPo<`)WC}SvUcCHo$f=Vj85@ciCjR;JXSe5$ zlPQZXzkK4M;WBB>x^>T1g|3c>3Olx>t-byI(`V1VF-ow0c-F=%J<W{cOqwx63d4_s zIcDvzUcC~VHEUMI!$Yk{``X*vfBrti<XKczHS78L`QKNDtP*69W7w5G&#n5~8_%}> zvZ-EQlYM=CYp$=0-4eAnCo?nAL`v1O&1vDwxz^=%ObJX5x}G_Ac6_`)^XSf<J5Q#r zRq>phnwqN2%gfv2wz%hL(wi4AKCHg_N}FLH>x7pD7W1+-G+7MtHs54pYIJyWppkh# zvjp=8!3NRuwnC}t>FULY%DGyV7#^fEsCt{-3QE}+F==0g{DD_bYxb=XkE&<-$uxPM UrS>Eb1_lNOPgg&ebxsLQ02xA^o&W#< literal 0 HcmV?d00001 diff --git a/archipack/icons/polygons.png b/archipack/icons/polygons.png new file mode 100644 index 0000000000000000000000000000000000000000..b434068cf201cc7dedda0f02bc59dd2fecee1e98 GIT binary patch literal 242 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}Y)RhkE(~TMXi@j;5d#AQ zXMsm#F#`k3dk|(+zkWxafq{X&#M9T6{UMVy51W{K1J@}A28Mc17srr@!*8b<avd<> zVV?TP{N{i8*Zy0br~A1!c9cv=x3=W4N>6#l>R9?c;TIFbxy{q#;-;3oZ#<jIz$}qq z?XYG_7fXWMY9j`Q8FwY`+Rm6c!*a@9$@l5MXRY*Fet?0kBI(2}zr>>ny302$JkxTT nQDlEQNcaaUL$3MMj=Wi06ldz1&5dDTU|{fc^>bP0l+XkKg+Nm1 literal 0 HcmV?d00001 diff --git a/archipack/icons/selection.png b/archipack/icons/selection.png new file mode 100644 index 0000000000000000000000000000000000000000..e4a7e82bb49b6bbb0a21214c8cbd5915aecfcf83 GIT binary patch literal 1021 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}Y)RhkE(~TMXi@j;5d#AQ zXMsm#F#`k3dk|(+zkWxafq{X&#M9T6{UMVy4~xlyYnp2q7?`^}T^vIq4!@o5?~xoX za;*Nn>Y|_*Cax_#kuMZFj3Oq@I%B56vP8k_?zD+FdG(g)ZJKv8;z;dG*M%M2qPqe# zgH{KcZ}VNIwRY+<16jq5zGr)8Wm<Qh^uG5p=^gh$sbe#?1iffH{k!<w<C>c1^WM)} z{<FsJq5K99l|`{?=lBHp+_Pr=IVXF6X>#ehoB!B9+-E8HZ>J)3okd}KvwQ!8XTB95 z9!N3FP%A0jyQ}_(z^qvb`S0V?7VMUml9s-C>z2~|{f_14clL6(1zIvxFI|*0OYLOJ zBp)?5waJQ8y##sKX3m<Gl;*a0?b_aD%eVx7)Yu6we6!18)!PHs4LyB*U8(Hs?2pQJ zvkD|gSFBd!nZm>>AXv0h=3>T_sZ*ye+P`({)VXtImn~l|DJl7pVY%LP?&X&|mt|fx zF6iTAH(>U#nmg_DwDZrG?%#huNtv(W)YE;d1m-fnW{{Sat}nioV>WI2bk0^M3mLxY zS|^w9T6a5ZX$#v2i3vAv-rVH4ckkXgbLI%}ura#%Tt36_o;hkO1J?n~4=N&Dd(A3} ziY8Uq_=Sgu%YKj)2uxk`N~eM|M@5J;Xyujc=2$)P-@kwV_*>T?_h4S;tZ+7kEcupG zZ<{}x85%Yoewa{PEc`%1;IM(-{WkFkxf{$qKY#w*wQ%7=?&fRPufKf#db8YoW_xDN z12fO2#WU`B!x4I`OQ|VQ!ew#bwQJW-)jj_HyKwj2H}BqwHOL;lBY$A!x^Q#ej03z6 zk{$Z``ns$NYHD;E9Txnksi@H4<>h6X!ywNn&+xv=MsB6Yrt|CP&6CT_&lh)i_Uu^~ zXLM8)%Y=;^H$K{Z^r)-%3_JE1hvw_A*YY?>YQASNYHn^;Qo3~UVxsCV(Fv`stx6pq zKNg-Y=id<e#XCSPf_ct?^0Q~p8W|WQsB$o$c38D<U)-aQJ32a!BpI$2VSO;|`s=At zXIk_d1B_;-WM*<cV1Ho#Kyt&RvuWIi4<BB$Zr!DuH&5QYIdkQa7rBBfSMloFo?wb+ zl3(@2zfocX_lyJ54-0<mbx>%y_4oEF;k@Ucf39MTsQMuC!)fD3o*!OQ4_^8cePFhi zd67d@Y&O#YmN<su1Gx{XA7pRkG<^{LKv;EuY1iE22e#eKThGbB_>X0gS5iGoU7)(! z0mcu#R^=t3vuYpgT=&|PG2tw`J)?if+|R1}wlIC&ous|+9ru)Zr^9ocy_QPuDEPDK gnD*&-sn6o!*WSLrv_x8%fq{X+)78&qol`;+0AcLS_W%F@ literal 0 HcmV?d00001 diff --git a/archipack/icons/slab.png b/archipack/icons/slab.png new file mode 100644 index 0000000000000000000000000000000000000000..292ea52efa442feef358e2da68a6468829558646 GIT binary patch literal 1620 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 literal 0 HcmV?d00001 diff --git a/archipack/icons/stair.png b/archipack/icons/stair.png new file mode 100644 index 0000000000000000000000000000000000000000..5ce4d7054f3bb1e15262bbf51e014ca2fff11208 GIT binary patch literal 1486 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 literal 0 HcmV?d00001 diff --git a/archipack/icons/truss.png b/archipack/icons/truss.png new file mode 100644 index 0000000000000000000000000000000000000000..72ca91579c1c627ec088935dfa44e05cb0534fdf GIT binary patch literal 1462 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}Y)RhkE(~Ds(|LD20|NtR zfk$L90|U1(2s1Lwnj^u$z`$PO>Fdh=kd1{&N57$~=_LaLgKTC<M2T~LZf<H`30S{# zQEFmIYKlU6W=V#EyQgnJie4%^0|Tp+r;B4q#NoZuV>2eFiyW_4ue^Ni$Sia3U9mg1 z{RrH1A*4O7$5ocqaj)Q!hpz7L*_ukEOB6W1E~+q;+<l36Tj#yV56M+ex1I7)Unc(1 zX@&DgW!;GGt=oQ9?A~U0cJ`du8B>;AlM3F>+<2gR-sd^R|DOLjUl|bURqvp_z<<77 zXy{cB6`}o=pVI<DIol8GTv#6(dfiDnZoT*eZwGfbw@0h8_<49#^6u=oxGUhgqo|V7 zT%|?H$;tW+I~yAtD^!Fy^_IOZv3kvr?%9wUG?iJ@>!aD?2V11t7XDw({FyWQ)nn^o zh56_A^XVnqsfIdzn3pI~zUbnLDjT`l4-XpO-QWK|<lv7ze!jkE=Vq9!+Maj!Qie&? zD&_UNYvkr1P07pSYY5Qza%%hO>H6pY`!BzI<=VAPxwp3&&0QP4z3Azwsn_HFxUpwt zWijli`l|K&+uLZ9qr$?%EKH2Yjvp_yn%n(y#p$O(2NadAItp;G%$PUNEwrAm{qUzZ zH;pw5Sa0TtG0Z<dz3XVw?vT>mtLBAW{V`G5ok3;xS*uk|3v_y>xASk^y0w-qhjCJ> z8_(g*X=i829q>^RlD@t++FffZ(}AZ&8`tPn{QF}m*WZ43cRBNqM)la(*ypEuEM)i| zG(SGxZ~pu5KNqd3Sxk2NXD>xxcL@AbS059)z2k(Rpyl@$7nP-?q)wF_JAQnw(x$r4 z&(89Pd}T7wQ!1UBa(#uu-%I!JzYo58Z`-zQ|E5iwW~UG_>z;$cfiq`#^0rqyC$fDJ z-_#m=$Sq1&&~mOH!>`}JjaRL@d-v{?W#x=MY69<Gz52Dz#3`0d_|=CG8$u75*jZWK zva+!7&=eF+$l6o(_SSPHCmk{ELrYf8(t9;cApEI-MBBss{QUYk9x4?-J}55BOp-XJ zZQ!aTIDgf*t`qNdb#?0**Rsw?i8M{PZMHrC{<3}h<W6t8u`!u_Tdw%r=rw7RmSyH` z(^)NSk$SuA?xwWHj)Yli>V9()&d;+gyuD3YJ9lIGCDxNElL9najz9hx9J=4!xA@AY zHB&`z#BokI{d8GFXv32t%avVS>1k;*=FPjuI>&oykUZ06Z<&uvgH|4V_wHSO<DON| zrY(BZHM8LTy}cqzJ1c&Cc$mz+aDVmpJfRGBA%|?Mxywpq!g(E?ot>RR%ezjfD}D0T z5&OMk<HnCu{a(I!QNc99LnUH&neMby#(?PP-Ftd9=AS?P_xJbLp<lOG*!=tY>+5Ux zemUDHvkVuO?YDjX{rT(m+vi1wDPLf;NJ~$@?653LuE0X(+_PuTdJkN9@Zf=GsI8{b z+yg6<#C?2xM0)23U0v^U`N$@ndc_9u12PKw>DO*=UzmNgOZ0Z*vKqVnMHv|{%BriY zZ+eMwuo$iSwR#mF^L(+suq%uExH>Yv{rdX)_@t9h+G6y+pI_!X+wRrdx4O!UV)UlV zNlRPv{^6}De1C86kw#|rI-N6%-fZd$aQU-3e0^QfiwlZ+aeGeu{QUgo>(`Ip+}!-R zXT_>#kB|5Nmyncnboo%V_vo7yxwl>AR>bl!EuQ3g<I0sIt=!_zUVeFSuz8MUF<XO= zT63ZVOS9vhJaY%R4NV6V?k@G7-cw>_%X)^P^k+@ny<3eZPkO$3{d#(gm-q_{hWL$q zHgf*50iSnmKP;Vn;Q43YqmMt9g~p2VwY!Ec-B5DoJkw>9Zhrs3(2rGnnQEAKY`VE- z;}NTXn?buy9Qn=ogR??@26wB|WbYTR>c5&4GbtuS7v9iGvz~m?hbg#0#C&zoPw_LG V=H~7$u{8pfpPsIMF6*2UngGYIut)#^ literal 0 HcmV?d00001 diff --git a/archipack/icons/union.png b/archipack/icons/union.png new file mode 100644 index 0000000000000000000000000000000000000000..11b114724061362dae1ecb94b11aa3ac9eede927 GIT binary patch literal 1102 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}Y)RhkE(~TMXi@j;5d#AQ zXMsm#F#`k3dk|(+zkWxafq{X&#M9T6{UMVy535+t+DJ|Y2IkwIE{-7)hu==~_XvrW zId1>`+>M^aFK4H7nzI`U`mAj%oHJ8s(aHoTroxA6LPeLIVmO+FcC75Zy)BnZgsD|Q zROrZlY4NU~N}K0$^r>+DVpDqVTfS@mb_=U-=dYVQ_LkctAM<YK_dn+>?|=S1_x|^q ztqBGif4I%;?bk1ToX$RD_H6r0N1YcY+}dY#Jm)|2k9?+#?B?FyWTp=vm2Ik6($5|E z`};V<flUi`MQxS0X$W4Pkoi*F{lM$iJ9lEz($hWFxIb1sJSS^#Hl9I2o&D8oMY(?A z{^OlTlS)cUFW%>`to-ohrN5s951VVt7WvCAaeJK@GV}AN*G9+2iq1c;-moJ^kFm~s zcGu-iwrSfL_cK2|aN|bAs@-eW^c+3v`s>%PjNDw-AD170OpsukwM^EL#UkVV8-?xp zt2N%ddw1&kG#|C43l};ve5{zz+WP*yBoCXavRr}H!&|0T=gph<Z`u8{&4yN1r=C1f zk+<8ia&_~yupeJub|@vxI&o=ImxH%=_n#BHcJF2t*ezT6jc4PUW{wn=1Dhs%EnTvR z$&knS(#8xEt*ckBGIiWve`!V`m&4RMS66S?xY1EZ?DxX`#~&wFR?aN2ICFaXhbNp7 z)27~xU6A=oCT4%#56d67s;a9Cci%m8=1f4yl4l0K#-DFr$P4e8F`2o!xw-w9udnaI zOetGi+Z#ECPKrBfB4g7!U7S8TL<wne6yItr+04l5z-nM<SomT?fkn^x=a0X>w*T-u z^oid*mhI9dvFGO)>T-7;ov_U;ly~;**(;g1@7=3A(L?3y-hKP@{FVpb$$N6ocFF$u zLg(DvwJEMQKU#eHvy;idz+ghuh9yf}7+$`3;qg~mPEO1r@1Dz|KvOdTjuiF-CKI>k z3$`<7W@QNpPCRwWi}8qo$cBXr7m9S8JLkvv<YThR%sY=8a~LATKE0ED^i}ET(W94c z+|X!XVe)Wh4hm?nkm2ieTdbtCY1=k6zvaOtvXPO2f+wPcpFLe!bm!Ksq?DAF&(9AU zJou8iqmbFh*s-|S`0?Y%A)%oYJ^r{B8^3v@k+wN~^NwYWhZ!ew*wk>Cng+!2EnWKH zbK|<h@AURm7`m$nDcZ}giwT_i;Tf;W<dbh0-h6hf+<7avZr`oSTH6*Ug=)#T$j0yQ zA0A@0C}fJ!`})t)wqVAROA9v$<lPP0{i*8VB~{fZ*9GhN-rZ|$WmUcD<*g{duwVX7 zS@kyt6W_Og?pSJQHLcRp&<y0^Ecm7qvuDMTHK{AFT)CoQxcYU~H->Lt9aB|9LNumr zt~XnKdZ~k`$chZfwyfJZ@f>Tvocb{TSE~NQ`;+5;m+rp*f9Lu8E`M*IigB~AV_;xl N@O1TaS?83{1ORkW16lw8 literal 0 HcmV?d00001 diff --git a/archipack/icons/wall.png b/archipack/icons/wall.png new file mode 100644 index 0000000000000000000000000000000000000000..1335a590b819908c8180f1b38e1e762349590dcc GIT binary patch literal 637 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}Y)RhkE)4%caKYZ?lNlHo zI14-?iy0WWg+Z8+Vb&Z81_lQ95>H=O_J^zj+#-7A68*vq3=FcFArU3c`MJ5Nc_j=W z;9QiNSdyBeP@Y+mq2TW68<3)x%Fe*R_|VhEF(l&f(Xfqqha7ln%NFPG`1Cl4tPFW| zr*Ucgfh)Xb)3`obzdgXdq4Xj5jex8A35#QAwajfa`BbXFsXJl9Zk9yNORm%2rvLtV zuB9f>(#nao=v-%%-gI@*xqi!q5{@x!Qel>mWDf9JdY0o4L)?Rd-)pwa-KMu<^2Rlb z{6c)#`}X%AwccmTfAH3ED-MAQxtjaOZ|6+AwPRhkMpw~aF*XOox}UST-+$Gg>{(SN z!+!g%*cqPN+g3A9=u$bX$k6coqDJV}DBXx@tZ8kjqQ$W?4U1wA@pLfB?Yg`8!0fZn za*nkhI{MtzF@k|{M#J(r{_dYL-9KxdcSvrH^4+~)O>pDhqxDX&({fo7%vpKbp4{)B z&g9^d!00036wbhKM5dvE*UP~rWD;{HYy5K7-LpHiYuI@`PAE0Rbhl{j`+Z{#)8U67 zmZfk%`p50eBal)MFujE-tzo^>p(lz$n;pJZ?N#BtEX39F#rwf7y{T%7GXlTLWZi8M zV2s$Full{w`CD7!kE&|*?+kLKOr}!2i|2kgabjEUV#yO#n|xP!s;NY*4YLnr)KCc& zX|4Z%{PD(BX_~rTdZ#y8&Ykt9E0)D!;sJ5S1;RUm7}OezdD)sBKhJqsV)fVO+~2zW hGq)C{-l$W39)G91=fJwliD{rz;OXk;vd$@?2>@~#0qFn$ literal 0 HcmV?d00001 diff --git a/archipack/icons/window.png b/archipack/icons/window.png new file mode 100644 index 0000000000000000000000000000000000000000..74be2e0ee154c9994b03593b4e0652ff108b0037 GIT binary patch literal 579 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}Y)RhkE)4%caKYZ?lNlHo zI14-?iy0WWg+Z8+Vb&Z81_lQ95>H=O_J^zj+#+g~huIYw7#L(TLn2C?^K)}k^GX;% zz_}<ju_QG`p**uBL&4qCHy}kXm7Rfsak;09V@Sl|x6}9M9WoF&w%_=nzjmpoo7t7# zjq5LV>Q<{?JHrvW$60OLJD*xcxdqD6OZDD{l|{9!_ik#kWS^|Ilv(|*!0Ata|J?j| zmyyAP>s-c`xhdb*H+hQ{)CZI}dVF(vvYL}2D*4wFwgW{CybW<D1sM;RH{{(-aQwJf z-u2Zl`PZg02RReGH$7&!ZJ{q$$^F1q<K3r1vtae?1j#83FD#OBJh#PWb)Lu$WSn*~ zrP)_qxI*LlIfb`#pKrY9k|J<Jc-dsmxUkiGH$Fegw46aMP;b9MbF=k>O}{Um;6B2> zM&4CJ>v1WIU6`w7KI^Kebup4XE4>?6eamOaJIC9=eSq~r%rZNd^1~1BZU1}7lxxA> z`<4^5UtVGRU?3;&Gh=zCa^u#NCD%fwrkI6&ebJQM`XWClU+~1zMN^(K?vAlP`YiL6 z&+SUv4cg~()HA1@e!BJAmM5t-vSG7WJZGtRMujkBGyP_92)@)}pnUO``ANBrbB`C+ zs488!lX&stvWZhvJfqZ2*0X2X9xXh-bVj(WP}@Sq%vJw1uc_sHk2}(mn6ZjQJ@b^v ZAI2pg+`gGNeR~OtOHWrnmvv4FO#tD$?Tr8c literal 0 HcmV?d00001 diff --git a/archipack/materialutils.py b/archipack/materialutils.py new file mode 100644 index 000000000..92497924b --- /dev/null +++ b/archipack/materialutils.py @@ -0,0 +1,169 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +import bpy + + +class MaterialUtils(): + + @staticmethod + def build_default_mat(name, color=(1.0, 1.0, 1.0)): + midx = bpy.data.materials.find(name) + if midx < 0: + mat = bpy.data.materials.new(name) + mat.diffuse_color = color + else: + mat = bpy.data.materials[midx] + return mat + + @staticmethod + def add_wall2_materials(obj): + int_mat = MaterialUtils.build_default_mat('inside', (0.5, 1.0, 1.0)) + out_mat = MaterialUtils.build_default_mat('outside', (0.5, 1.0, 0.5)) + oth_mat = MaterialUtils.build_default_mat('cuts', (1.0, 0.2, 0.2)) + alt1_mat = MaterialUtils.build_default_mat('wall_alternative1', (1.0, 0.2, 0.2)) + alt2_mat = MaterialUtils.build_default_mat('wall_alternative2', (1.0, 0.2, 0.2)) + alt3_mat = MaterialUtils.build_default_mat('wall_alternative3', (1.0, 0.2, 0.2)) + alt4_mat = MaterialUtils.build_default_mat('wall_alternative4', (1.0, 0.2, 0.2)) + alt5_mat = MaterialUtils.build_default_mat('wall_alternative5', (1.0, 0.2, 0.2)) + obj.data.materials.append(out_mat) + obj.data.materials.append(int_mat) + obj.data.materials.append(oth_mat) + obj.data.materials.append(alt1_mat) + obj.data.materials.append(alt2_mat) + obj.data.materials.append(alt3_mat) + obj.data.materials.append(alt4_mat) + obj.data.materials.append(alt5_mat) + + @staticmethod + def add_wall_materials(obj): + int_mat = MaterialUtils.build_default_mat('inside', (0.5, 1.0, 1.0)) + out_mat = MaterialUtils.build_default_mat('outside', (0.5, 1.0, 0.5)) + oth_mat = MaterialUtils.build_default_mat('cuts', (1.0, 0.2, 0.2)) + obj.data.materials.append(out_mat) + obj.data.materials.append(int_mat) + obj.data.materials.append(oth_mat) + + @staticmethod + def add_slab_materials(obj): + out_mat = MaterialUtils.build_default_mat('Slab_bottom', (0.5, 1.0, 1.0)) + int_mat = MaterialUtils.build_default_mat('Slab_top', (1.0, 0.2, 0.2)) + oth_mat = MaterialUtils.build_default_mat('Slab_side', (0.5, 1.0, 0.5)) + obj.data.materials.append(out_mat) + obj.data.materials.append(int_mat) + obj.data.materials.append(oth_mat) + + @staticmethod + def add_stair_materials(obj): + cei_mat = MaterialUtils.build_default_mat('Stair_ceiling', (0.5, 1.0, 1.0)) + whi_mat = MaterialUtils.build_default_mat('Stair_white', (1.0, 1.0, 1.0)) + con_mat = MaterialUtils.build_default_mat('Stair_concrete', (0.5, 0.5, 0.5)) + wood_mat = MaterialUtils.build_default_mat('Stair_wood', (0.28, 0.2, 0.1)) + metal_mat = MaterialUtils.build_default_mat('Stair_metal', (0.4, 0.4, 0.4)) + glass_mat = MaterialUtils.build_default_mat('Stair_glass', (0.2, 0.2, 0.2)) + glass_mat.use_transparency = True + glass_mat.alpha = 0.5 + glass_mat.game_settings.alpha_blend = 'ADD' + obj.data.materials.append(cei_mat) + obj.data.materials.append(whi_mat) + obj.data.materials.append(con_mat) + obj.data.materials.append(wood_mat) + obj.data.materials.append(metal_mat) + obj.data.materials.append(glass_mat) + + @staticmethod + def add_fence_materials(obj): + wood_mat = MaterialUtils.build_default_mat('Fence_wood', (0.28, 0.2, 0.1)) + metal_mat = MaterialUtils.build_default_mat('Fence_metal', (0.4, 0.4, 0.4)) + glass_mat = MaterialUtils.build_default_mat('Fence_glass', (0.2, 0.2, 0.2)) + glass_mat.use_transparency = True + glass_mat.alpha = 0.5 + glass_mat.game_settings.alpha_blend = 'ADD' + obj.data.materials.append(wood_mat) + obj.data.materials.append(metal_mat) + obj.data.materials.append(glass_mat) + + @staticmethod + def add_floor_materials(obj): + con_mat = MaterialUtils.build_default_mat('Floor_grout', (0.5, 0.5, 0.5)) + alt1_mat = MaterialUtils.build_default_mat('Floor_alt1', (0.5, 1.0, 1.0)) + alt2_mat = MaterialUtils.build_default_mat('Floor_alt2', (1.0, 1.0, 1.0)) + alt3_mat = MaterialUtils.build_default_mat('Floor_alt3', (0.28, 0.2, 0.1)) + alt4_mat = MaterialUtils.build_default_mat('Floor_alt4', (0.5, 1.0, 1.0)) + alt5_mat = MaterialUtils.build_default_mat('Floor_alt5', (1.0, 1.0, 0.5)) + alt6_mat = MaterialUtils.build_default_mat('Floor_alt6', (0.28, 0.5, 0.1)) + alt7_mat = MaterialUtils.build_default_mat('Floor_alt7', (0.5, 1.0, 0.5)) + alt8_mat = MaterialUtils.build_default_mat('Floor_alt8', (1.0, 0.2, 1.0)) + alt9_mat = MaterialUtils.build_default_mat('Floor_alt9', (0.28, 0.2, 0.5)) + alt10_mat = MaterialUtils.build_default_mat('Floor_alt10', (0.5, 0.2, 0.1)) + obj.data.materials.append(con_mat) + obj.data.materials.append(alt1_mat) + obj.data.materials.append(alt2_mat) + obj.data.materials.append(alt3_mat) + obj.data.materials.append(alt4_mat) + obj.data.materials.append(alt5_mat) + obj.data.materials.append(alt6_mat) + obj.data.materials.append(alt7_mat) + obj.data.materials.append(alt8_mat) + obj.data.materials.append(alt9_mat) + obj.data.materials.append(alt10_mat) + + @staticmethod + def add_handle_materials(obj): + metal_mat = MaterialUtils.build_default_mat('metal', (0.4, 0.4, 0.4)) + obj.data.materials.append(metal_mat) + + @staticmethod + def add_door_materials(obj): + int_mat = MaterialUtils.build_default_mat('door_inside', (0.7, 0.2, 0.2)) + out_mat = MaterialUtils.build_default_mat('door_outside', (0.7, 0.2, 0.7)) + glass_mat = MaterialUtils.build_default_mat('glass', (0.2, 0.2, 0.2)) + metal_mat = MaterialUtils.build_default_mat('metal', (0.4, 0.4, 0.4)) + glass_mat.use_transparency = True + glass_mat.alpha = 0.5 + glass_mat.game_settings.alpha_blend = 'ADD' + obj.data.materials.append(out_mat) + obj.data.materials.append(int_mat) + obj.data.materials.append(glass_mat) + obj.data.materials.append(metal_mat) + + @staticmethod + def add_window_materials(obj): + int_mat = MaterialUtils.build_default_mat('window_inside', (0.7, 0.2, 0.2)) + out_mat = MaterialUtils.build_default_mat('window_outside', (0.7, 0.2, 0.7)) + glass_mat = MaterialUtils.build_default_mat('glass', (0.2, 0.2, 0.2)) + metal_mat = MaterialUtils.build_default_mat('metal', (0.4, 0.4, 0.4)) + tablet_mat = MaterialUtils.build_default_mat('tablet', (0.2, 0.2, 0.2)) + blind_mat = MaterialUtils.build_default_mat('blind', (0.2, 0.0, 0.0)) + glass_mat.use_transparency = True + glass_mat.alpha = 0.5 + glass_mat.game_settings.alpha_blend = 'ADD' + obj.data.materials.append(out_mat) + obj.data.materials.append(int_mat) + obj.data.materials.append(glass_mat) + obj.data.materials.append(metal_mat) + obj.data.materials.append(tablet_mat) + obj.data.materials.append(blind_mat) diff --git a/archipack/panel.py b/archipack/panel.py new file mode 100644 index 000000000..c8898fe56 --- /dev/null +++ b/archipack/panel.py @@ -0,0 +1,715 @@ +# -*- 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 ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- + +from math import cos, sin, tan, sqrt, atan2, pi +from mathutils import Vector + + +class Panel(): + """ + Define a bevel profil + index: array associate each y with a coord circle and a x + x = array of x of unique points in the profil relative to origin (0, 0) is bottom left + y = array of y of all points in the profil relative to origin (0, 0) is bottom left + idmat = array of material index for each segment + when path is not closed, start and end caps are generated + + shape is the loft profile + path is the loft path + + Open shape: + + x = [0,1] + y = [0,1,1, 0] + index = [0, 0,1,1] + closed_shape = False + + 1 ____2 + | | + | | + | | + 0 3 + + Closed shape: + + x = [0,1] + y = [0,1,1, 0] + index = [0, 0,1,1] + closed_shape = True + + 1 ____2 + | | + | | + |____| + 0 3 + + Side Caps (like glass for window): + + x = [0,1] + y = [0,1,1, 0.75, 0.25, 0] + index = [0, 0,1,1,1,1] + closed_shape = True + side_caps = [3,4] + + 1 ____2 ____ + | 3|__cap__| | + | 4|_______| | + |____| |____| + 0 5 + + """ + def __init__(self, closed_shape, index, x, y, idmat, side_cap_front=-1, side_cap_back=-1, closed_path=True, + subdiv_x=0, subdiv_y=0, user_path_verts=0, user_path_uv_v=None): + + self.closed_shape = closed_shape + self.closed_path = closed_path + self.index = index + self.x = x + self.y = y + self.idmat = idmat + self.side_cap_front = side_cap_front + self.side_cap_back = side_cap_back + self.subdiv_x = subdiv_x + self.subdiv_y = subdiv_y + self.user_path_verts = user_path_verts + self.user_path_uv_v = user_path_uv_v + + @property + def n_pts(self): + return len(self.y) + + @property + def profil_faces(self): + """ + number of faces for each section + """ + if self.closed_shape: + return len(self.y) + else: + return len(self.y) - 1 + + @property + def uv_u(self): + """ + uvs of profil (absolute value) + """ + x = [self.x[i] for i in self.index] + x.append(x[0]) + y = [y for y in self.y] + y.append(y[0]) + uv_u = [] + uv = 0 + uv_u.append(uv) + for i in range(len(self.index)): + dx = x[i + 1] - x[i] + dy = y[i + 1] - y[i] + uv += sqrt(dx * dx + dy * dy) + uv_u.append(uv) + return uv_u + + def path_sections(self, steps, path_type): + """ + number of verts and faces sections along path + """ + n_path_verts = 2 + if path_type in ['QUADRI', 'RECTANGLE']: + n_path_verts = 4 + self.subdiv_x + 2 * self.subdiv_y + if self.closed_path: + n_path_verts += self.subdiv_x + elif path_type in ['ROUND', 'ELLIPSIS']: + n_path_verts = steps + 3 + elif path_type == 'CIRCLE': + n_path_verts = steps + elif path_type == 'TRIANGLE': + n_path_verts = 3 + elif path_type == 'PENTAGON': + n_path_verts = 5 + elif path_type == 'USER_DEFINED': + n_path_verts = self.user_path_verts + if self.closed_path: + n_path_faces = n_path_verts + else: + n_path_faces = n_path_verts - 1 + return n_path_verts, n_path_faces + + def n_verts(self, steps, path_type): + n_path_verts, n_path_faces = self.path_sections(steps, path_type) + return self.n_pts * n_path_verts + + ############################ + # Geomerty + ############################ + + def _intersect_line(self, center, basis, x): + """ upper intersection of line parallel to y axis and a triangle + where line is given by x origin + top by center, basis size as float + return float y of upper intersection point + + center.x and center.y are absolute + a 0 center.x lie on half size + a 0 center.y lie on basis + """ + if center.x > 0: + dx = x - center.x + else: + dx = center.x - x + p = center.y / basis + return center.y + dx * p + + def _intersect_triangle(self, center, basis, x): + """ upper intersection of line parallel to y axis and a triangle + where line is given by x origin + top by center, basis size as float + return float y of upper intersection point + + center.x and center.y are absolute + a 0 center.x lie on half size + a 0 center.y lie on basis + """ + if x > center.x: + dx = center.x - x + sx = 0.5 * basis - center.x + else: + dx = x - center.x + sx = 0.5 * basis + center.x + if sx == 0: + sx = basis + p = center.y / sx + return center.y + dx * p + + def _intersect_circle(self, center, radius, x): + """ upper intersection of line parallel to y axis and a circle + where line is given by x origin + circle by center, radius as float + return float y of upper intersection point, float angle + """ + dx = x - center.x + d = (radius * radius) - (dx * dx) + if d <= 0: + if x > center.x: + return center.y, 0 + else: + return center.y, pi + else: + y = sqrt(d) + return center.y + y, atan2(y, dx) + + def _intersect_elipsis(self, center, radius, x): + """ upper intersection of line parallel to y axis and an ellipsis + where line is given by x origin + circle by center, radius.x and radius.y semimajor and seminimor axis (half width and height) as float + return float y of upper intersection point, float angle + """ + dx = x - center.x + d2 = dx * dx + A = 1 / radius.y / radius.y + C = d2 / radius.x / radius.x - 1 + d = - 4 * A * C + if d <= 0: + if x > center.x: + return center.y, 0 + else: + return center.y, pi + else: + y0 = sqrt(d) / 2 / A + d = (radius.x * radius.x) - d2 + y = sqrt(d) + return center.y + y0, atan2(y, dx) + + def _intersect_arc(self, center, radius, x_left, x_right): + y0, a0 = self._intersect_circle(center, radius.x, x_left) + y1, a1 = self._intersect_circle(center, radius.x, x_right) + da = (a1 - a0) + if da < -pi: + da += 2 * pi + if da > pi: + da -= 2 * pi + return y0, y1, a0, da + + def _intersect_arc_elliptic(self, center, radius, x_left, x_right): + y0, a0 = self._intersect_elipsis(center, radius, x_left) + y1, a1 = self._intersect_elipsis(center, radius, x_right) + da = (a1 - a0) + if da < -pi: + da += 2 * pi + if da > pi: + da -= 2 * pi + return y0, y1, a0, da + + def _get_ellispe_coords(self, steps, offset, center, origin, size, radius, x, pivot, bottom_y=0): + """ + Rectangle with single arc on top + """ + x_left = size.x / 2 * (pivot - 1) + x + x_right = size.x / 2 * (pivot + 1) - x + cx = center.x - origin.x + cy = offset.y + center.y - origin.y + y0, y1, a0, da = self._intersect_arc_elliptic(center, radius, origin.x + x_left, origin.x + x_right) + da /= steps + coords = [] + # bottom left + if self.closed_path: + coords.append((offset.x + x_left, offset.y + x + bottom_y)) + else: + coords.append((offset.x + x_left, offset.y + bottom_y)) + # top left + coords.append((offset.x + x_left, offset.y + y0 - origin.y)) + for i in range(1, steps): + a = a0 + i * da + coords.append((offset.x + cx + cos(a) * radius.x, cy + sin(a) * radius.y)) + # top right + coords.append((offset.x + x_right, offset.y + y1 - origin.y)) + # bottom right + if self.closed_path: + coords.append((offset.x + x_right, offset.y + x + bottom_y)) + else: + coords.append((offset.x + x_right, offset.y + bottom_y)) + return coords + + def _get_arc_coords(self, steps, offset, center, origin, size, radius, x, pivot, bottom_y=0): + """ + Rectangle with single arc on top + """ + x_left = size.x / 2 * (pivot - 1) + x + x_right = size.x / 2 * (pivot + 1) - x + cx = offset.x + center.x - origin.x + cy = offset.y + center.y - origin.y + y0, y1, a0, da = self._intersect_arc(center, radius, origin.x + x_left, origin.x + x_right) + da /= steps + coords = [] + + # bottom left + if self.closed_path: + coords.append((offset.x + x_left, offset.y + x + bottom_y)) + else: + coords.append((offset.x + x_left, offset.y + bottom_y)) + + # top left + coords.append((offset.x + x_left, offset.y + y0 - origin.y)) + + for i in range(1, steps): + a = a0 + i * da + coords.append((cx + cos(a) * radius.x, cy + sin(a) * radius.x)) + + # top right + coords.append((offset.x + x_right, offset.y + y1 - origin.y)) + + # bottom right + if self.closed_path: + coords.append((offset.x + x_right, offset.y + x + bottom_y)) + else: + coords.append((offset.x + x_right, offset.y + bottom_y)) + + return coords + + def _get_circle_coords(self, steps, offset, center, origin, radius): + """ + Full circle + """ + cx = offset.x + center.x - origin.x + cy = offset.y + center.y - origin.y + a = -2 * pi / steps + return [(cx + cos(i * a) * radius.x, cy + sin(i * a) * radius.x) for i in range(steps)] + + def _get_rectangular_coords(self, offset, size, x, pivot, bottom_y=0): + coords = [] + + x_left = offset.x + size.x / 2 * (pivot - 1) + x + x_right = offset.x + size.x / 2 * (pivot + 1) - x + + if self.closed_path: + y0 = offset.y + x + bottom_y + else: + y0 = offset.y + bottom_y + y1 = offset.y + size.y - x + + dy = (y1 - y0) / (1 + self.subdiv_y) + dx = (x_right - x_left) / (1 + self.subdiv_x) + + # bottom left + # coords.append((x_left, y0)) + + # subdiv left + for i in range(self.subdiv_y + 1): + coords.append((x_left, y0 + i * dy)) + + # top left + # coords.append((x_left, y1)) + + # subdiv top + for i in range(self.subdiv_x + 1): + coords.append((x_left + dx * i, y1)) + + # top right + # coords.append((x_right, y1)) + # subdiv right + for i in range(self.subdiv_y + 1): + coords.append((x_right, y1 - i * dy)) + + # subdiv bottom + if self.closed_path: + for i in range(self.subdiv_x + 1): + coords.append((x_right - dx * i, y0)) + else: + # bottom right + coords.append((x_right, y0)) + + return coords + + def _get_vertical_rectangular_trapezoid_coords(self, offset, center, origin, size, basis, x, pivot, bottom_y=0): + """ + Rectangular trapezoid vertical + basis is the full width of a triangular area the trapezoid lie into + center.y is the height of triagular area from top + center.x is the offset from basis center + + |\ + | \ + |__| + """ + coords = [] + x_left = size.x / 2 * (pivot - 1) + x + x_right = size.x / 2 * (pivot + 1) - x + sx = x * sqrt(basis * basis + center.y * center.y) / basis + dy = size.y + offset.y - sx + y0 = self._intersect_line(center, basis, origin.x + x_left) + y1 = self._intersect_line(center, basis, origin.x + x_right) + # bottom left + if self.closed_path: + coords.append((offset.x + x_left, offset.y + x + bottom_y)) + else: + coords.append((offset.x + x_left, offset.y + bottom_y)) + # top left + coords.append((offset.x + x_left, dy - y0)) + # top right + coords.append((offset.x + x_right, dy - y1)) + # bottom right + if self.closed_path: + coords.append((offset.x + x_right, offset.y + x + bottom_y)) + else: + coords.append((offset.x + x_right, offset.y + bottom_y)) + return coords + + def _get_horizontal_rectangular_trapezoid_coords(self, offset, center, origin, size, basis, x, pivot, bottom_y=0): + """ + Rectangular trapeze horizontal + basis is the full width of a triangular area the trapezoid lie into + center.y is the height of triagular area from top to basis + center.x is the offset from basis center + ___ + | \ + |____\ + + TODO: correct implementation + """ + raise NotImplementedError + + def _get_pentagon_coords(self, offset, center, origin, size, basis, x, pivot, bottom_y=0): + """ + TODO: correct implementation + /\ + / \ + | | + |____| + """ + raise NotImplementedError + + def _get_triangle_coords(self, offset, center, origin, size, basis, x, pivot, bottom_y=0): + coords = [] + x_left = offset.x + size.x / 2 * (pivot - 1) + x + x_right = offset.x + size.x / 2 * (pivot + 1) - x + + # bottom left + if self.closed_path: + coords.append((x_left, offset.y + x + bottom_y)) + else: + coords.append((x_left, offset.y + bottom_y)) + # top center + coords.append((center.x, offset.y + center.y)) + # bottom right + if self.closed_path: + coords.append((x_right, offset.y + x + bottom_y)) + else: + coords.append((x_right, offset.y + bottom_y)) + return coords + + def _get_horizontal_coords(self, offset, size, x, pivot): + coords = [] + x_left = offset.x + size.x / 2 * (pivot - 1) + x_right = offset.x + size.x / 2 * (pivot + 1) + # left + coords.append((x_left, offset.y + x)) + # right + coords.append((x_right, offset.y + x)) + return coords + + def _get_vertical_coords(self, offset, size, x, pivot): + coords = [] + x_left = offset.x + size.x / 2 * (pivot - 1) + x + # top + coords.append((x_left, offset.y + size.y)) + # bottom + coords.append((x_left, offset.y)) + return coords + + def choose_a_shape_in_tri(self, center, origin, size, basis, pivot): + """ + Choose wich shape inside either a tri or a pentagon + """ + cx = (0.5 * basis + center.x) - origin.x + cy = center.y - origin.y + x_left = size.x / 2 * (pivot - 1) + x_right = size.x / 2 * (pivot + 1) + y0 = self.intersect_triangle(cx, cy, basis, x_left) + y1 = self.intersect_triangle(cx, cy, basis, x_right) + if (y0 == 0 and y1 == 0) or ((y0 == 0 or y1 == 0) and (y0 == cy or y1 == cy)): + return 'TRIANGLE' + elif x_right <= cx or x_left >= cx: + # single side of triangle + # may be horizontal or vertical rectangular trapezoid + # horizontal if size.y < center.y + return 'QUADRI' + else: + # both sides of triangle + # may be horizontal trapezoid or pentagon + # horizontal trapezoid if size.y < center.y + return 'PENTAGON' + + ############################ + # Vertices + ############################ + + def vertices(self, steps, offset, center, origin, size, radius, + angle_y, pivot, shape_z=None, path_type='ROUND', axis='XZ'): + + verts = [] + if shape_z is None: + shape_z = [0 for x in self.x] + if path_type == 'ROUND': + coords = [self._get_arc_coords(steps, offset, center, origin, + size, Vector((radius.x - x, 0)), x, pivot, shape_z[i]) for i, x in enumerate(self.x)] + elif path_type == 'ELLIPSIS': + coords = [self._get_ellispe_coords(steps, offset, center, origin, + size, Vector((radius.x - x, radius.y - x)), x, pivot, shape_z[i]) for i, x in enumerate(self.x)] + elif path_type == 'QUADRI': + coords = [self._get_vertical_rectangular_trapezoid_coords(offset, center, origin, + size, radius.x, x, pivot) for i, x in enumerate(self.x)] + elif path_type == 'HORIZONTAL': + coords = [self._get_horizontal_coords(offset, size, x, pivot) + for i, x in enumerate(self.x)] + elif path_type == 'VERTICAL': + coords = [self._get_vertical_coords(offset, size, x, pivot) + for i, x in enumerate(self.x)] + elif path_type == 'CIRCLE': + coords = [self._get_circle_coords(steps, offset, center, origin, Vector((radius.x - x, 0))) + for i, x in enumerate(self.x)] + else: + coords = [self._get_rectangular_coords(offset, size, x, pivot, shape_z[i]) + for i, x in enumerate(self.x)] + # vertical panel (as for windows) + if axis == 'XZ': + for i in range(len(coords[0])): + for j, p in enumerate(self.index): + x, z = coords[p][i] + y = self.y[j] + verts.append((x, y, z)) + # horizontal panel (table and so on) + elif axis == 'XY': + for i in range(len(coords[0])): + for j, p in enumerate(self.index): + x, y = coords[p][i] + z = self.y[j] + verts.append((x, y, z)) + return verts + + ############################ + # Faces + ############################ + + def _faces_cap(self, faces, n_path_verts, offset): + if self.closed_shape and not self.closed_path: + last_point = offset + self.n_pts * n_path_verts - 1 + faces.append(tuple([offset + i for i in range(self.n_pts)])) + faces.append(tuple([last_point - i for i in range(self.n_pts)])) + + def _faces_closed(self, n_path_faces, offset): + faces = [] + n_pts = self.n_pts + for i in range(n_path_faces): + k0 = offset + i * n_pts + if self.closed_path and i == n_path_faces - 1: + k1 = offset + else: + k1 = k0 + n_pts + for j in range(n_pts - 1): + faces.append((k1 + j, k1 + j + 1, k0 + j + 1, k0 + j)) + # close profile + faces.append((k1 + n_pts - 1, k1, k0, k0 + n_pts - 1)) + return faces + + def _faces_open(self, n_path_faces, offset): + faces = [] + n_pts = self.n_pts + for i in range(n_path_faces): + k0 = offset + i * n_pts + if self.closed_path and i == n_path_faces - 1: + k1 = offset + else: + k1 = k0 + n_pts + for j in range(n_pts - 1): + faces.append((k1 + j, k1 + j + 1, k0 + j + 1, k0 + j)) + return faces + + def _faces_side(self, faces, n_path_verts, start, reverse, offset): + n_pts = self.n_pts + vf = [offset + start + n_pts * f for f in range(n_path_verts)] + if reverse: + faces.append(tuple(reversed(vf))) + else: + faces.append(tuple(vf)) + + def faces(self, steps, offset=0, path_type='ROUND'): + n_path_verts, n_path_faces = self.path_sections(steps, path_type) + if self.closed_shape: + faces = self._faces_closed(n_path_faces, offset) + else: + faces = self._faces_open(n_path_faces, offset) + if self.side_cap_front > -1: + self._faces_side(faces, n_path_verts, self.side_cap_front, False, offset) + if self.side_cap_back > -1: + self._faces_side(faces, n_path_verts, self.side_cap_back, True, offset) + self._faces_cap(faces, n_path_verts, offset) + return faces + + ############################ + # Uvmaps + ############################ + + def uv(self, steps, center, origin, size, radius, angle_y, pivot, x, x_cap, path_type='ROUND'): + uvs = [] + n_path_verts, n_path_faces = self.path_sections(steps, path_type) + if path_type in ['ROUND', 'ELLIPSIS']: + x_left = size.x / 2 * (pivot - 1) + x + x_right = size.x / 2 * (pivot + 1) - x + if path_type == 'ELLIPSIS': + y0, y1, a0, da = self._intersect_arc_elliptic(center, radius, x_left, x_right) + else: + y0, y1, a0, da = self._intersect_arc(center, radius, x_left, x_right) + uv_r = abs(da) * radius.x / steps + uv_v = [uv_r for i in range(steps)] + uv_v.insert(0, y0 - origin.y) + uv_v.append(y1 - origin.y) + uv_v.append(size.x) + elif path_type == 'USER_DEFINED': + uv_v = self.user_path_uv_v + elif path_type == 'CIRCLE': + uv_r = 2 * pi * radius.x / steps + uv_v = [uv_r for i in range(steps + 1)] + elif path_type == 'QUADRI': + dy = 0.5 * tan(angle_y) * size.x + uv_v = [size.y - dy, size.x, size.y + dy, size.x] + elif path_type == 'HORIZONTAL': + uv_v = [size.y] + elif path_type == 'VERTICAL': + uv_v = [size.y] + else: + dx = size.x / (1 + self.subdiv_x) + dy = size.y / (1 + self.subdiv_y) + uv_v = [] + for i in range(self.subdiv_y + 1): + uv_v.append(dy * (i + 1)) + for i in range(self.subdiv_x + 1): + uv_v.append(dx * (i + 1)) + for i in range(self.subdiv_y + 1): + uv_v.append(dy * (i + 1)) + for i in range(self.subdiv_x + 1): + uv_v.append(dx * (i + 1)) + # uv_v = [size.y, size.x, size.y, size.x] + + uv_u = self.uv_u + if self.closed_shape: + n_pts = self.n_pts + else: + n_pts = self.n_pts - 1 + v0 = 0 + # uvs parties rondes + for i in range(n_path_faces): + v1 = v0 + uv_v[i] + for j in range(n_pts): + u0 = uv_u[j] + u1 = uv_u[j + 1] + uvs.append([(u0, v1), (u1, v1), (u1, v0), (u0, v0)]) + v0 = v1 + if self.side_cap_back > -1 or self.side_cap_front > -1: + if path_type == 'ROUND': + # rectangle with top part round + coords = self._get_arc_coords(steps, Vector((0, 0, 0)), center, + origin, size, Vector((radius.x - x_cap, 0)), x_cap, pivot, x_cap) + elif path_type == 'CIRCLE': + # full circle + coords = self._get_circle_coords(steps, Vector((0, 0, 0)), center, + origin, Vector((radius.x - x_cap, 0))) + elif path_type == 'ELLIPSIS': + coords = self._get_ellispe_coords(steps, Vector((0, 0, 0)), center, + origin, size, Vector((radius.x - x_cap, radius.y - x_cap)), x_cap, pivot, x_cap) + elif path_type == 'QUADRI': + coords = self._get_vertical_rectangular_trapezoid_coords(Vector((0, 0, 0)), center, + origin, size, radius.x, x_cap, pivot) + # coords = self._get_trapezoidal_coords(0, origin, size, angle_y, x_cap, pivot, x_cap) + else: + coords = self._get_rectangular_coords(Vector((0, 0, 0)), size, x_cap, pivot, 0) + if self.side_cap_front > -1: + uvs.append(list(coords)) + if self.side_cap_back > -1: + uvs.append(list(reversed(coords))) + + if self.closed_shape and not self.closed_path: + coords = [(self.x[self.index[i]], y) for i, y in enumerate(self.y)] + uvs.append(coords) + uvs.append(list(reversed(coords))) + return uvs + + ############################ + # Material indexes + ############################ + + def mat(self, steps, cap_front_id, cap_back_id, path_type='ROUND'): + n_path_verts, n_path_faces = self.path_sections(steps, path_type) + n_profil_faces = self.profil_faces + idmat = [] + for i in range(n_path_faces): + for mat in range(n_profil_faces): + idmat.append(self.idmat[mat]) + if self.side_cap_front > -1: + idmat.append(cap_front_id) + if self.side_cap_back > -1: + idmat.append(cap_back_id) + if self.closed_shape and not self.closed_path: + idmat.append(self.idmat[0]) + idmat.append(self.idmat[0]) + return idmat diff --git a/archipack/presets/archipack_door/160x200_dual.png b/archipack/presets/archipack_door/160x200_dual.png new file mode 100644 index 0000000000000000000000000000000000000000..ef4fac84e9bab47038f87b58acf89e32d91bd6b8 GIT binary patch literal 10252 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 PU|{fc^>bP0l+XkK1!8qE literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_door/160x200_dual.py b/archipack/presets/archipack_door/160x200_dual.py new file mode 100644 index 000000000..7a9e5ebc2 --- /dev/null +++ b/archipack/presets/archipack_door/160x200_dual.py @@ -0,0 +1,23 @@ +import bpy +d = bpy.context.active_object.data.archipack_door[0] + +d.handle = 'BOTH' +d.panels_distrib = 'REGULAR' +d.direction = 0 +d.frame_y = 0.029999999329447746 +d.door_y = 0.019999999552965164 +d.flip = False +d.panels_y = 3 +d.frame_x = 0.10000000149011612 +d.model = 2 +d.door_offset = 0.0 +d.x = 1.600000023841858 +d.z = 2.0 +d.hole_margin = 0.10000000149011612 +d.panel_border = 0.12999999523162842 +d.panels_x = 2 +d.panel_spacing = 0.10000000149011612 +d.chanfer = 0.004999999888241291 +d.panel_bottom = 0.17000000178813934 +d.n_panels = 2 +d.y = 0.20000000298023224 diff --git a/archipack/presets/archipack_door/400x240_garage.png b/archipack/presets/archipack_door/400x240_garage.png new file mode 100644 index 0000000000000000000000000000000000000000..660b1d705443662baebbb1fb58ae680b6e257d57 GIT binary patch literal 10492 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 JS?83{1OQqEa$Eoa literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_door/400x240_garage.py b/archipack/presets/archipack_door/400x240_garage.py new file mode 100644 index 000000000..2060cc3b1 --- /dev/null +++ b/archipack/presets/archipack_door/400x240_garage.py @@ -0,0 +1,23 @@ +import bpy +d = bpy.context.active_object.data.archipack_door[0] + +d.handle = 'NONE' +d.panels_distrib = 'REGULAR' +d.direction = 0 +d.frame_y = 0.029999999329447746 +d.door_y = 0.019999999552965164 +d.flip = False +d.panels_y = 1 +d.frame_x = 0.10000000149011612 +d.model = 1 +d.door_offset = 0.0 +d.x = 4.0 +d.z = 2.4000000953674316 +d.hole_margin = 0.10000000149011612 +d.panel_border = 0.0010000000474974513 +d.panels_x = 24 +d.panel_spacing = 0.0010000000474974513 +d.chanfer = 0.004999999888241291 +d.panel_bottom = 0.0 +d.n_panels = 1 +d.y = 0.20000000298023224 diff --git a/archipack/presets/archipack_door/80x200.png b/archipack/presets/archipack_door/80x200.png new file mode 100644 index 0000000000000000000000000000000000000000..e2bf6f5ccc444f72e8e6f4e442d6f758c9690efd GIT binary patch literal 7840 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 literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_door/80x200.py b/archipack/presets/archipack_door/80x200.py new file mode 100644 index 000000000..a29e3ddc0 --- /dev/null +++ b/archipack/presets/archipack_door/80x200.py @@ -0,0 +1,23 @@ +import bpy +d = bpy.context.active_object.data.archipack_door[0] + +d.handle = 'BOTH' +d.panels_distrib = 'REGULAR' +d.direction = 0 +d.frame_y = 0.029999999329447746 +d.door_y = 0.019999999552965164 +d.flip = False +d.panels_y = 1 +d.frame_x = 0.10000000149011612 +d.model = 0 +d.door_offset = 0.0 +d.x = 0.800000011920929 +d.z = 2.0 +d.hole_margin = 0.10000000149011612 +d.panel_border = 0.20000000298023224 +d.panels_x = 1 +d.panel_spacing = 0.10000000149011612 +d.chanfer = 0.004999999888241291 +d.panel_bottom = 0.0 +d.n_panels = 1 +d.y = 0.20000000298023224 diff --git a/archipack/presets/archipack_fence/glass_panels.png b/archipack/presets/archipack_fence/glass_panels.png new file mode 100644 index 0000000000000000000000000000000000000000..4478afa6ff78bfd2838e843190d1462ed3b7b7db GIT binary patch literal 7106 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 literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_fence/glass_panels.py b/archipack/presets/archipack_fence/glass_panels.py new file mode 100644 index 000000000..2d150b71e --- /dev/null +++ b/archipack/presets/archipack_fence/glass_panels.py @@ -0,0 +1,67 @@ +import bpy +d = bpy.context.active_object.data.archipack_fence[0] + +d.rail_expand = True +d.shape = 'RECTANGLE' +d.rail = False +d.radius = 0.699999988079071 +d.user_defined_resolution = 12 +d.handrail = False +d.handrail_x = 0.07999999076128006 +d.subs_alt = 0.10000000149011612 +d.handrail_extend = 0.0 +d.idmat_subs = '0' +d.rail_alt = (0.20000000298023224, 0.699999988079071, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0) +d.subs_x = 0.029999999329447746 +d.subs_offset_x = 0.0 +d.handrail_y = 0.03999999910593033 +d.user_defined_subs_enable = True +d.rail_x = (0.030000001192092896, 0.029999999329447746, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.post_y = 0.009999999776482582 +d.handrail_alt = 1.0 +d.subs_y = 0.09999999403953552 +d.idmat_panel = '2' +d.panel_expand = True +d.panel_x = 0.009999999776482582 +d.idmats_expand = True +d.idmat_post = '0' +d.idmat_handrail = '1' +d.user_defined_post_enable = True +d.x_offset = 0.0 +d.subs_z = 0.7999998927116394 +d.subs_bottom = 'STEP' +d.post_expand = True +d.subs_expand = False +d.rail_offset = (-0.009999999776482582, -0.009999999776482582, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +d.post = False +d.handrail_radius = 0.029999999329447746 +d.rail_n = 2 +d.rail_mat.clear() +item_sub_1 = d.rail_mat.add() +item_sub_1.name = '' +item_sub_1.index = '0' +item_sub_1 = d.rail_mat.add() +item_sub_1.name = '' +item_sub_1.index = '0' +d.parts_expand = False +d.angle_limit = 0.39269909262657166 +d.post_spacing = 1.5 +d.handrail_expand = True +d.subs = False +d.handrail_slice_right = True +d.panel_alt = 0.0 +d.user_defined_subs = '' +d.panel_dist = 0.009999999776482582 +d.handrail_slice = True +d.panel = True +d.subs_spacing = 0.07000000774860382 +d.panel_z = 1.0 +d.handrail_profil = 'CIRCLE' +d.handrail_offset = 0.0 +d.da = 1.5707963705062866 +d.post_z = 1.0 +d.rail_z = (0.07000000029802322, 0.07000000029802322, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.post_x = 0.03999999910593033 +d.user_defined_post = '' +d.panel_offset_x = 0.0 +d.post_alt = 0.0 diff --git a/archipack/presets/archipack_fence/inox_glass_concrete.png b/archipack/presets/archipack_fence/inox_glass_concrete.png new file mode 100644 index 0000000000000000000000000000000000000000..e90314975618f03cda32cfee5ddd1ad0db81abf0 GIT binary patch literal 7835 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 Mr>mdKI;Vst04$TN`2YX_ literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_fence/inox_glass_concrete.py b/archipack/presets/archipack_fence/inox_glass_concrete.py new file mode 100644 index 000000000..80d3fb6c3 --- /dev/null +++ b/archipack/presets/archipack_fence/inox_glass_concrete.py @@ -0,0 +1,64 @@ +import bpy +d = bpy.context.active_object.data.archipack_fence[0] + +d.rail_expand = True +d.shape = 'RECTANGLE' +d.rail = True +d.radius = 0.699999988079071 +d.user_defined_resolution = 12 +d.handrail = True +d.handrail_x = 0.07999999076128006 +d.subs_alt = 0.10000000149011612 +d.handrail_extend = 0.0 +d.idmat_subs = '0' +d.rail_alt = (-0.2999999523162842, 0.699999988079071, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0) +d.subs_x = 0.029999999329447746 +d.subs_offset_x = 0.0 +d.handrail_y = 0.03999999910593033 +d.user_defined_subs_enable = True +d.rail_x = (0.19999998807907104, 0.029999999329447746, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.post_y = 0.009999999776482582 +d.handrail_alt = 1.0 +d.subs_y = 0.09999999403953552 +d.idmat_panel = '2' +d.panel_expand = True +d.panel_x = 0.009999999776482582 +d.idmats_expand = True +d.idmat_post = '0' +d.idmat_handrail = '1' +d.user_defined_post_enable = True +d.x_offset = 0.0 +d.subs_z = 0.7999998927116394 +d.subs_bottom = 'STEP' +d.post_expand = True +d.subs_expand = False +d.rail_offset = (-0.04999999701976776, -0.009999999776482582, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +d.post = False +d.handrail_radius = 0.029999999329447746 +d.rail_n = 1 +d.rail_mat.clear() +item_sub_1 = d.rail_mat.add() +item_sub_1.name = '' +item_sub_1.index = '0' +d.parts_expand = False +d.angle_limit = 0.39269909262657166 +d.post_spacing = 1.5 +d.handrail_expand = True +d.subs = False +d.handrail_slice_right = True +d.panel_alt = 0.0 +d.user_defined_subs = '' +d.panel_dist = 0.009999999776482582 +d.handrail_slice = True +d.panel = True +d.subs_spacing = 0.07000000774860382 +d.panel_z = 1.0 +d.handrail_profil = 'CIRCLE' +d.handrail_offset = 0.0 +d.da = 1.5707963705062866 +d.post_z = 1.0 +d.rail_z = (0.3199999928474426, 0.07000000029802322, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.post_x = 0.03999999910593033 +d.user_defined_post = '' +d.panel_offset_x = 0.0 +d.post_alt = 0.0 diff --git a/archipack/presets/archipack_fence/metal.png b/archipack/presets/archipack_fence/metal.png new file mode 100644 index 0000000000000000000000000000000000000000..b6a24339f99cc5aca6383ee4511a2209409a9b0e GIT binary patch literal 10234 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 literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_fence/metal.py b/archipack/presets/archipack_fence/metal.py new file mode 100644 index 000000000..5e7ecbfdd --- /dev/null +++ b/archipack/presets/archipack_fence/metal.py @@ -0,0 +1,67 @@ +import bpy +d = bpy.context.active_object.data.archipack_fence[0] + +d.rail_expand = True +d.shape = 'RECTANGLE' +d.rail = True +d.radius = 0.699999988079071 +d.user_defined_resolution = 12 +d.handrail = True +d.handrail_x = 0.03999999910593033 +d.subs_alt = 0.15000000596046448 +d.handrail_extend = 0.10000000149011612 +d.idmat_subs = '1' +d.rail_alt = (0.15000000596046448, 0.8500000238418579, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0) +d.subs_x = 0.019999999552965164 +d.subs_offset_x = 0.0 +d.handrail_y = 0.03999999910593033 +d.user_defined_subs_enable = True +d.rail_x = (0.030000001192092896, 0.029999999329447746, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.post_y = 0.03999999910593033 +d.handrail_alt = 1.0 +d.subs_y = 0.019999999552965164 +d.idmat_panel = '2' +d.panel_expand = False +d.panel_x = 0.009999999776482582 +d.idmats_expand = False +d.idmat_post = '1' +d.idmat_handrail = '0' +d.user_defined_post_enable = True +d.x_offset = 0.0 +d.subs_z = 0.699999988079071 +d.subs_bottom = 'STEP' +d.post_expand = False +d.subs_expand = True +d.rail_offset = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +d.post = True +d.handrail_radius = 0.019999999552965164 +d.rail_n = 2 +d.rail_mat.clear() +item_sub_1 = d.rail_mat.add() +item_sub_1.name = '' +item_sub_1.index = '1' +item_sub_1 = d.rail_mat.add() +item_sub_1.name = '' +item_sub_1.index = '1' +d.parts_expand = False +d.angle_limit = 0.39269909262657166 +d.post_spacing = 1.5 +d.handrail_expand = False +d.subs = True +d.handrail_slice_right = True +d.panel_alt = 0.20999997854232788 +d.user_defined_subs = '' +d.panel_dist = 0.03999999910593033 +d.handrail_slice = True +d.panel = False +d.subs_spacing = 0.10000000149011612 +d.panel_z = 0.6000000238418579 +d.handrail_profil = 'SQUARE' +d.handrail_offset = 0.0 +d.da = 1.5707963705062866 +d.post_z = 1.0 +d.rail_z = (0.019999999552965164, 0.019999999552965164, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.post_x = 0.03999999910593033 +d.user_defined_post = '' +d.panel_offset_x = 0.0 +d.post_alt = 0.0 diff --git a/archipack/presets/archipack_fence/metal_glass.png b/archipack/presets/archipack_fence/metal_glass.png new file mode 100644 index 0000000000000000000000000000000000000000..16020ec483dcdbdbf55ff592a0c92dfe7e51e6ee GIT binary patch literal 9582 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%0D+sYyb0!(T~FA zB7eQtN&b57vBPYo29~*XO-}EBFI?Iyo11a_mX`HZ_tkZ;Ej|^Sl&Jpt^=ReNPuo5& zjxNozuPKvh-r6!<Y{j45$N&B4J~t=!&ne?R-`b_0s^fmFHVzZn>Q%aF;hvvs_OJ-d z+E}-={g~6Q;<t$@FTU6%tM9S8=XN;Ui2d&w|M-<Qk59!v%DsGa{nA^m>AU56L(5L) zpI;oF`~8pQy=y(8yy41I*X>{HV%{kEdim1%byL@F(c16ZwpaD%hpkt`Wc^Qu%-SbZ z3o_~ERQGS!FT{TOX=bRKrqj0BAXD4de(5J?@91Xr>4m>CPx^m#o}Kl0asJO0pMEU# z{kkcs)`Wq9#UHd5;NppP^RjIhlqUa_INnyb^!uLNKZ`HLZo2*X*Np0amtyRBHs`Ot z_(97z{^06Ayk8%r>jxjd=qr7;=YHtvyd2-J)$=}@MfGl--rs*=_1fI3y?IZvt3}GD z=j3&-ye=F6IPFqq@{(=Af3^DeuRT)vWYK@`Emx0+#QVH>cva$ghV9;47FzcLZ=S8W z{AsdORPN!~-Otm@bxsNTMQ7IDk65`$`qb?6D@CNFZ2vCKaSf`KI3EA&mek~pn_LdB z|J<jut4L(e;!V{zBA!38oUn0KK&i>~{+g$Yzq0P1zVh*x{#By>TWzoQ`&7(q{wR|h zUbXe^>opJe_U1jCJZ<fj3zypVU6cR&V&A^x)`;7=e>IPnJoB2<7XS6Keb=__C+jm_ z>h~^Bxgb&%TJBuyTt9t}YW>vjdbMeF*|~B*!e%^p^(g1H|3_zU>r$5%wFh66<!|lI zy18~+&rcWccx&@{lU8n7b^CHXZ`{|L>))>1vwiOIlw+&TP5<>U)wyRmSDk+F@t4cz zZra!@c6aBrx{Bbx^TLl$oWJ<3*ZQ)mx!-%kA76am{bTtv2fn{w`>R)7(EBU#LRPPL z_1!r8J%_hleLtnwYxXPkc^{S?E8Y_O;;LRHt8S@t<!8xX_N(r_d1@<sKIAv&x5p=P zv#a;k#r$pCzHRQNz)bJI^QIRj8HDu&n@>5Lp0W4Oq;vMl`CkKPxZcotJp25v1-1Ws zuPig)`obdk%Y|#(uEv#U>;C<)<>hyq<fYSZ25i5)`fl8Fjf#(fw|Cw+DZ`roHSmW1 zk)VK?H>UV(HNLujOO`tKM(^9Fk1R9$t2Mdq{On2ZGeft(TC6=W{!y+9f1TTVg;L{L zk6ZUWdA*{t-HyvQS8K;gR*tpP*FRoamywygeA`3ct+u^!8IZuZ-x`)${qxl2^{qKs zOJBF|OL?zRQ7dCQP4(WwzC#l8jrR0j^WWE(ZC~azJ6n1A+th6r5B0|#yCt&clzaT5 zubYZLP2IPoI704s?aVG~qvAamt{>a@N@I=VlKL6j_&3RK-s!@<K7Vywh4&WCnJ2>U zN!^;0v-JBmZU6oHPnMUQIQ!9Q{-x^ZIQjb_GVA|`Nj#MDc<cYsx%;8jy1zT8m)w5w z=ecW|{_W33emZ;OenhE$bLx*fzxuJ}Ov|XH-_5l23m0g1`u~spTvdJbzSKQE!+qx> zu6o`#4_kGRH}zj9r+roNu8FJv+<O1B{i$l-y*U5*(%<WPpJ&+GnoWP<z2`_w^rv8} z{MXs1o+Z}oWeYQn_<fsc?c}@H-oO3!Dfo}-t#x~}wqJQ)S!%PLFQsay<xSOJLi%>o z{^)$X@y<KvTghX~xEJ304n_Y7lThFL+2X~^-93*jHvO&WlAF0D<<k4xnsG~gza}q! zo*MHrJEM5h)O)w@O?6+VpYvDf+mE@QMen`YeuyI@dnVVOPvtMao4u-7C%=2@{mWYV zf48JO{}sGP>-OhfQKR4@+qI|kKDsTwdwfcN{ONnMZv2-Q@fB+ES!TG9fq{X+)78&q Iol`;+08{kEhyVZp literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_fence/metal_glass.py b/archipack/presets/archipack_fence/metal_glass.py new file mode 100644 index 000000000..fb5149cb7 --- /dev/null +++ b/archipack/presets/archipack_fence/metal_glass.py @@ -0,0 +1,67 @@ +import bpy +d = bpy.context.active_object.data.archipack_fence[0] + +d.rail_expand = True +d.shape = 'RECTANGLE' +d.rail = True +d.radius = 0.699999988079071 +d.user_defined_resolution = 12 +d.handrail = True +d.handrail_x = 0.03999999910593033 +d.subs_alt = 0.0 +d.handrail_extend = 0.10000000149011612 +d.idmat_subs = '1' +d.rail_alt = (0.15000000596046448, 0.8500000238418579, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0) +d.subs_x = 0.019999999552965164 +d.subs_offset_x = 0.0 +d.handrail_y = 0.03999999910593033 +d.user_defined_subs_enable = True +d.rail_x = (0.030000001192092896, 0.029999999329447746, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.post_y = 0.03999999910593033 +d.handrail_alt = 1.0 +d.subs_y = 0.019999999552965164 +d.idmat_panel = '2' +d.panel_expand = False +d.panel_x = 0.009999999776482582 +d.idmats_expand = False +d.idmat_post = '1' +d.idmat_handrail = '0' +d.user_defined_post_enable = True +d.x_offset = 0.0 +d.subs_z = 1.0 +d.subs_bottom = 'STEP' +d.post_expand = True +d.subs_expand = False +d.rail_offset = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +d.post = True +d.handrail_radius = 0.019999999552965164 +d.rail_n = 2 +d.rail_mat.clear() +item_sub_1 = d.rail_mat.add() +item_sub_1.name = '' +item_sub_1.index = '1' +item_sub_1 = d.rail_mat.add() +item_sub_1.name = '' +item_sub_1.index = '1' +d.parts_expand = False +d.angle_limit = 0.39269909262657166 +d.post_spacing = 1.5 +d.handrail_expand = False +d.subs = False +d.handrail_slice_right = True +d.panel_alt = 0.20999997854232788 +d.user_defined_subs = '' +d.panel_dist = 0.03999999910593033 +d.handrail_slice = True +d.panel = True +d.subs_spacing = 0.10000000149011612 +d.panel_z = 0.6000000238418579 +d.handrail_profil = 'SQUARE' +d.handrail_offset = 0.0 +d.da = 1.5707963705062866 +d.post_z = 1.0 +d.rail_z = (0.019999999552965164, 0.019999999552965164, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.post_x = 0.03999999910593033 +d.user_defined_post = '' +d.panel_offset_x = 0.0 +d.post_alt = 0.0 diff --git a/archipack/presets/archipack_fence/wood.png b/archipack/presets/archipack_fence/wood.png new file mode 100644 index 0000000000000000000000000000000000000000..a1706f297fc24530193f0c7290e716cee43f917b GIT binary patch literal 13183 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 literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_fence/wood.py b/archipack/presets/archipack_fence/wood.py new file mode 100644 index 000000000..9a9a42d95 --- /dev/null +++ b/archipack/presets/archipack_fence/wood.py @@ -0,0 +1,67 @@ +import bpy +d = bpy.context.active_object.data.archipack_fence[0] + +d.user_defined_post = '' +d.handrail_offset = 0.0 +d.post_spacing = 1.5 +d.post_z = 1.0 +d.idmats_expand = True +d.rail_alt = (0.20000000298023224, 0.699999988079071, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0) +d.idmat_handrail = '0' +d.post_alt = 0.0 +d.handrail_expand = True +d.panel_x = 0.009999999776482582 +d.idmat_panel = '2' +d.rail_z = (0.07000000029802322, 0.07000000029802322, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.subs_y = 0.09999999403953552 +d.handrail_radius = 0.019999999552965164 +d.handrail_extend = 0.10000000149011612 +d.subs_alt = 0.10000000149011612 +d.idmat_subs = '0' +d.handrail_y = 0.03999999910593033 +d.user_defined_post_enable = True +d.rail = True +d.handrail_profil = 'SQUARE' +d.post_x = 0.059999994933605194 +d.handrail = True +d.da = 1.5707963705062866 +d.user_defined_subs_enable = True +d.subs_expand = True +d.shape = 'RECTANGLE' +d.angle_limit = 0.39269909262657166 +d.panel_alt = 0.20999997854232788 +d.post_expand = True +d.subs_bottom = 'STEP' +d.handrail_slice_right = True +d.handrail_alt = 1.0 +d.subs_z = 0.7999998927116394 +d.user_defined_subs = '' +d.rail_x = (0.030000001192092896, 0.029999999329447746, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.parts_expand = False +d.idmat_post = '0' +d.panel_offset_x = 0.0 +d.rail_n = 2 +d.panel_z = 0.6000000238418579 +d.handrail_x = 0.07999999076128006 +d.subs_spacing = 0.14000000059604645 +d.post = True +d.rail_mat.clear() +item_sub_1 = d.rail_mat.add() +item_sub_1.name = '' +item_sub_1.index = '0' +item_sub_1 = d.rail_mat.add() +item_sub_1.name = '' +item_sub_1.index = '0' +d.handrail_slice = True +d.panel = False +d.x_offset = 0.0 +d.rail_expand = True +d.rail_offset = (0.009999999776482582, 0.009999999776482582, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +d.panel_dist = 0.03999999910593033 +d.post_y = 0.059999994933605194 +d.subs = True +d.user_defined_resolution = 12 +d.subs_x = 0.029999999329447746 +d.radius = 0.699999988079071 +d.subs_offset_x = 0.0 +d.panel_expand = False diff --git a/archipack/presets/archipack_floor/herringbone_50x10.png b/archipack/presets/archipack_floor/herringbone_50x10.png new file mode 100644 index 0000000000000000000000000000000000000000..b6e7fe56713f2bdcc5ee2a65de012ce461fb2164 GIT binary patch literal 11148 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%0D?=DZ=C}Bz@ZHT87o6Y}R)H*#(j_%l55(*%BAP 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 CKa36l literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_floor/herringbone_50x10.py b/archipack/presets/archipack_floor/herringbone_50x10.py new file mode 100644 index 000000000..a1f196ef2 --- /dev/null +++ b/archipack/presets/archipack_floor/herringbone_50x10.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.space_l = 0.004999999888241291 +d.is_width_vary = False +d.offset_vary = 47.810237884521484 +d.is_ran_thickness = False +d.b_length = 2.0 +d.t_length = 0.30000001192092896 +d.space_w = 0.004999999888241291 +d.t_width_s = 0.10000000149011612 +d.b_length_s = 0.5 +d.is_grout = False +d.tile_types = '24' +d.offset = 50.0 +d.width_vary = 50.0 +d.spacing = 0.0010000000474974513 +d.is_offset = True +d.is_bevel = False +d.is_random_offset = True +d.bevel_amo = 0.001500000013038516 +d.thickness = 0.019999999552965164 +d.bevel_res = 1 +d.max_boards = 2 +d.b_width = 0.10000000149011612 +d.length_vary = 50.0 +d.ran_thickness = 50.0 +d.is_mat_vary = True +d.hb_direction = '1' +d.mat_vary = 3 +d.num_boards = 5 +d.t_width = 0.30000001192092896 +d.grout_depth = 0.0010000003967434168 +d.is_length_vary = False diff --git a/archipack/presets/archipack_floor/herringbone_p_50x10.png b/archipack/presets/archipack_floor/herringbone_p_50x10.png new file mode 100644 index 0000000000000000000000000000000000000000..1a2b2370eaaa413f5f2984c44fd1a52815615116 GIT binary patch literal 10924 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 literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_floor/herringbone_p_50x10.py b/archipack/presets/archipack_floor/herringbone_p_50x10.py new file mode 100644 index 000000000..088a22e41 --- /dev/null +++ b/archipack/presets/archipack_floor/herringbone_p_50x10.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.space_l = 0.004999999888241291 +d.is_width_vary = False +d.offset_vary = 47.810237884521484 +d.is_ran_thickness = False +d.b_length = 2.0 +d.t_length = 0.30000001192092896 +d.space_w = 0.004999999888241291 +d.t_width_s = 0.10000000149011612 +d.b_length_s = 0.5 +d.is_grout = False +d.tile_types = '23' +d.offset = 50.0 +d.width_vary = 50.0 +d.spacing = 0.0010000000474974513 +d.is_offset = True +d.is_bevel = False +d.is_random_offset = True +d.bevel_amo = 0.001500000013038516 +d.thickness = 0.019999999552965164 +d.bevel_res = 1 +d.max_boards = 2 +d.b_width = 0.10000000149011612 +d.length_vary = 50.0 +d.ran_thickness = 50.0 +d.is_mat_vary = True +d.hb_direction = '1' +d.mat_vary = 3 +d.num_boards = 5 +d.t_width = 0.30000001192092896 +d.grout_depth = 0.0010000003967434168 +d.is_length_vary = False diff --git a/archipack/presets/archipack_floor/parquet_15x3.png b/archipack/presets/archipack_floor/parquet_15x3.png new file mode 100644 index 0000000000000000000000000000000000000000..2b35d58b765186284c61c0d0f14e7cf28c58e79b GIT binary patch literal 13445 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 a_{pF64GH{{s(kVpK)}=0&t;ucLK6T8z8Rwc literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_floor/parquet_15x3.py b/archipack/presets/archipack_floor/parquet_15x3.py new file mode 100644 index 000000000..5711c93ae --- /dev/null +++ b/archipack/presets/archipack_floor/parquet_15x3.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.bevel_res = 1 +d.b_width = 0.029999999329447746 +d.is_bevel = False +d.hb_direction = '1' +d.is_width_vary = False +d.b_length = 2.0 +d.spacing = 0.0010000000474974513 +d.is_grout = False +d.num_boards = 5 +d.is_length_vary = False +d.thickness = 0.019999999552965164 +d.is_ran_thickness = False +d.is_random_offset = True +d.offset_vary = 47.810237884521484 +d.is_mat_vary = True +d.tile_types = '22' +d.length_vary = 50.0 +d.space_w = 0.004999999888241291 +d.ran_thickness = 50.0 +d.max_boards = 2 +d.t_width_s = 0.10000000149011612 +d.t_width = 0.30000001192092896 +d.t_length = 0.30000001192092896 +d.width_vary = 50.0 +d.mat_vary = 3 +d.grout_depth = 0.0010000003967434168 +d.is_offset = True +d.space_l = 0.004999999888241291 +d.bevel_amo = 0.001500000013038516 +d.offset = 50.0 +d.b_length_s = 2.0 diff --git a/archipack/presets/archipack_floor/planks_200x20.png b/archipack/presets/archipack_floor/planks_200x20.png new file mode 100644 index 0000000000000000000000000000000000000000..94a49c5789a2fe9d499563334f017b027a22728b GIT binary patch literal 11644 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 bk9$Xg=)3)Hr*<<iFfe$!`njxgN@xNAFN`0i literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_floor/planks_200x20.py b/archipack/presets/archipack_floor/planks_200x20.py new file mode 100644 index 000000000..bbea2e66c --- /dev/null +++ b/archipack/presets/archipack_floor/planks_200x20.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.bevel_res = 1 +d.b_width = 0.2 +d.is_bevel = True +d.hb_direction = '1' +d.is_width_vary = False +d.b_length = 2.0 +d.spacing = 0.002 +d.is_grout = False +d.num_boards = 4 +d.is_length_vary = False +d.thickness = 0.02 +d.is_ran_thickness = False +d.is_random_offset = True +d.offset_vary = 47.81 +d.is_mat_vary = True +d.tile_types = '21' +d.length_vary = 50.0 +d.space_w = 0.002 +d.ran_thickness = 50.0 +d.max_boards = 2 +d.t_width_s = 0.1 +d.t_width = 0.3 +d.t_length = 0.3 +d.width_vary = 50.0 +d.mat_vary = 3 +d.grout_depth = 0.001 +d.is_offset = True +d.space_l = 0.002 +d.bevel_amo = 0.0015 +d.offset = 50.0 +d.b_length_s = 2.0 diff --git a/archipack/presets/archipack_floor/tiles_15x15.png b/archipack/presets/archipack_floor/tiles_15x15.png new file mode 100644 index 0000000000000000000000000000000000000000..2a3d8633e632f4ae65e8bea9b7682ca50ca8cf79 GIT binary patch literal 12939 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 literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_floor/tiles_15x15.py b/archipack/presets/archipack_floor/tiles_15x15.py new file mode 100644 index 000000000..d3d244f90 --- /dev/null +++ b/archipack/presets/archipack_floor/tiles_15x15.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.b_width = 0.20000000298023224 +d.width_vary = 50.0 +d.t_width_s = 0.20000000298023224 +d.is_grout = True +d.tile_types = '1' +d.space_l = 0.004999999888241291 +d.is_length_vary = False +d.hb_direction = '1' +d.offset_vary = 50.0 +d.offset = 50.0 +d.spacing = 0.004999999888241291 +d.thickness = 0.10000000149011612 +d.bevel_res = 1 +d.is_offset = False +d.grout_depth = 0.0010000003967434168 +d.t_width = 0.15000000596046448 +d.is_ran_thickness = False +d.is_mat_vary = False +d.is_random_offset = False +d.space_w = 0.004999999888241291 +d.is_bevel = True +d.ran_thickness = 50.0 +d.max_boards = 2 +d.t_length = 0.15000000596046448 +d.b_length_s = 2.0 +d.bevel_amo = 0.001500000013038516 +d.is_width_vary = False +d.num_boards = 4 +d.length_vary = 50.0 +d.b_length = 0.800000011920929 +d.mat_vary = 1 diff --git a/archipack/presets/archipack_floor/tiles_60x30.png b/archipack/presets/archipack_floor/tiles_60x30.png new file mode 100644 index 0000000000000000000000000000000000000000..16cdf0f154ab28b4ccb7952fa031f4683cbcf59c GIT binary patch literal 11379 zcmeAS@N?(olHy`uVBq!ia0y~yV3@|hz>vbh1|p+Reqdl=;4JWnEM{Qf76xHPhFNnY z7#J8tOI#yL+%j`g8C<Ml3W`#TQ%j2Vl5$e>QVvMQ&Szj?kN_!gNi0caFfuSS*EcZJ zH!@T(G_f)=wK6n_xVkTpfq_8)q$VUYH<iJ_zzT{C-yBvu1hN$*=T?*mmNYyVD5?Z< zBS_FWF*mg+kpV(w{D1$Ffq{V=BoUmPnwQD|CZ8(Cg8U&25)MkuOGzz4SfgiP<Y#H0 z%fO((;OXKRQo(rl?#)RhmJ`)KoVe9}>$_}LW0Lf-7v1xs=OxIsg+E~8VdhCTExVWU z?pDr=%+1^KGBj4y-v6$(LiAYusUOFeUnqa%;*ypUdiKPdB_%4;cb|S*RQ_^~aOIb; z>+Ao@cN=^B|8acgg1Jw=tjUm-dao`k|4^>$cz0i4pPG91{8O4~e2wMO{0plyqS711 z&pCgp%&1AJ^_XL=8OE|Va}mGh2454S5BvU3DEVzTkM*AOV;hxyC%#Rv(0`?#75sAo zi}^)~*S|acZZG`#B2z}_d*lOK?m17S_O#2`@3D^5X-J=Fv_kVvyW9(rwaynM3g@%l z+kC+DUHc*KaQ$}U&^7Y}4kcSXSx|K+xP;#``$)aU9eLJ!&L6$*75{m+G(ve%oze1B z?J`F?>iecUU%cz`Sm4L<g8%<p%9l%MPp;}{WWT<x#_L1veCJP_I<F~a)LhwdzUz7I z$9Jh4e|6e=R!*L<yx^_%1WlQhx81%AY%#m|L*=oZ_dNfl5wj&uxAgr~Y_WM%vcI?A z`QqHg#Zgl7?C&-|`Dt@>mh=OQmCI9ej$FTAFH@7VxboJ=a}WP7d1R+~{`t{}<+2Iu zWs)oVP6~dTQ1iy?`xcY!7cF`J^>cssth76y)GKULzCU+E^8Lw1mXA*UUH66Gv0OZA z`NGY-|Nm$2NEdyr!!G^w#pm2FRS{1wb6D>vTUsd<wflmF=0AU}cgrGX&#-=(;CoAL z&ddud|CoHKs`-7vj`wWr%Q;hy>HV-@xg)x#ROs0@-d`#f)lWStrRN^_`ZF{0z1Qa@ zC3c)Y&QB`eksiD7<Smt!%_(mtco~_u&GU=5d%dw%=wtq@=W%+cc7Ljym(<Hr=4$dy zTJqH2bw8NwpY6W%^7q_nX}|veeP1766LH;DJnjZd&-8io4}9HHuKC^K*#rNk|Bnyt z{a$!j&Fy^Ag}od6otD4f-}zsTjaU4}_j}d(AFuEKSNidix4!PY>VH3vX0PA7ZO7AR zv&GHt7oC1ID|=mJ&8ttR#l_=mK61&;i_Pfy^6Pi*)7lU3wbwg81O-{M6zAJRFK(nC znfW`mfVJ#Q`BKAgw_S|CzTf}9E_g|JTy5#cN8S2*aclp~OrIZX^Yu#bp94vS2U)va z4|;@8NxEBmJ+|BW{hs2&-?`g$*X{rJ>uBoq*ljyD=t%wh_OMX?#s{mu+{=HDH&zF2 zta97NyQux_fo)$oV?JrhWZs_g-aGlo<bQW=Tfg74x#DrJxtRX6(5fE~+r|5Be?EEC z8y=S#yG}24p@#XrlFN@KdFv(iiRJJ48dlsXU-RSPqy7K>JU?3h_aT4x>(zhzg4Rjr z??^0sy>`3ay4~+~9Tg3a*|_7&CGYTSIeBv)ravjZ{8wh(#`DJ;t9>^fdLyIU$oaW= zLxS6nFRwr6T2$TGw}<=PfyECOdAy%`wDR>?b8-Fse?C3h`Fviq&95JiyN`R%6qB#n zuw#KpBU5bI)zG!tH$Js@@1O1z8*uH_rK0Na_{yh`4hOB8owuj(W18m8+fTLb|GRx( zjNj(Vg-3t?|2co{{h>`U-|yCbe{^qGp5FD!vs<s5ZNE8Xvx3{b<G(-b)w~~`_WOZ7 z%e!R}in}-yZ}Aq1&xn(l{h)e=Tz&lhwKnb6xdq*m|Ah&k-`Y8OUfr{qM-TVgZu_y| z)hQWWy|orEH|_ehB>u;)HB;U;2U)F~z`ASClxH88&yU*^UdmKiIBiZ*Y|TYi-DkhD zr(XPVnwj5Zi}{qIyJ6Gb+^c>Uc_-@Q&8y+@ckigB^3I+2Z`-GRwMzZD6<g(*z8@=I zBllAzxYjytS;7@><u`?IckVB)J@2@7y4#$B9alE1<xf?uTzcmw_fj8!n~z5xee9Ze zukP<xG38hvK`%zVsHml-XO1WDTy;mHwQ6HQOqkdkS+1zAFHt+sgui|y_diTeWbgIU zzXAom*oS(Rd+$7IzUtlF^82wd5qclnik4q$?zh?WgSk7bR`W)>-QSnXa_&3}J?g!m zQ{uS!(Z><nU)D*l+j!sZ^G43^v$S0l8iG$;X3LAVp0Pyp#+H-Sdusp2NS=!kd3kr1 zOW~!@sS{al`%hDv`D6PjU(OSnPIsa|EYkMdwV)vO<4S*1*Bxz}LT#rnm7j6l+f((6 zM!<X{Gd(f6b(bfyXfM<MalKoA-IrfQ?fFx+mlpU>Ia<Bsk>wUWJ=K4IYQO8g+w-_@ z+v6u@>(^MF(*OGD<!M{_;-?=h*e7qR{g{7f@3M#qevEB5Vx*5PyLEN>{!byduH?7y z&ha#PnXmVFNxJvMBOd-!x_>VaSvjpPxJUYQP{4%MT53i+?@acb>SHmzEIU$s^1r&B zr>AGJpO|af%CtG;-nGO>{e^)yyu0p|ZgsBNn6!<XTYtOu0rBdGl51Cc2d>kV=vqCG zDd<mmx1RZL_t_0av$u!bVf)v2J3szT*lb42?U^#KF5Xc)zhRel%!8*B3**g0@3mE% z9rJoVG2+#7iI=wzUASTY_2lwjEZg_p@;z`r%PHsoQ4gI<&W}{OLMChbWqtJZb@IOY z{Hay(uj)t@Vc*JiYqv!(b{*RkVCJRZo?Y6yv(u*QkZN48m$zZed;RtMr+zx*{Blx+ z?Vknx{(0Bj9DcSHwNAgXw=g#1yZ)7TUuKHOMd<wd8ZRz>b%#u5%`Uy&XJ3CWm|by0 zHFwhgnESaq)RWf;NLlBvK7Qhc`kAHq?|$!jKiTN}v8|T=RSR4GRJ^?-!@R{}B}<uS zWP_>eD}iT^>#jYXG)p7TRpxVEWJkF6xkVD&!wk+AZAh5HRKM`<!l+30gO;a{M;$s{ z8nov6sjOAikKb-r|IIFbX9}0BSf9|nz^+CA*1qCqPn~?bwCG{#vRiw54>|MORlHf0 zty4IgFLS;2_LmXL+-(0WBirZO&6g^_Kkt)WMgP8^XY<qKqW-TuxBJ@Mj>e~}lrHQr z@A?wL`I*1|&*Mi|qxP!)J$lr1qWJYE>kOrvraqq7pmBC8tM-C@>vTk>FSfhpDQ@v8 z;@P$!gA&=CO-XB}SJ&#T?PNWk`pEIlmzZ3(TY@ZAuG!|Z3(m;zd+^}aJdN(|>p2XE zcDFisURY4^V_}8J#GanXs#|sjl&9}J?JpW<eeSll+wNU4Yv*d;Nm%}D-N^;F&S`Dk z`QzI5eOnh97#ZF>Z)n1L+jw3XQ;DFEcj9-qiSyRKei^i^J)8Y|p61a&&gY+Y9aty9 z<HY1$Ia63A{J7Mg#Rc~9ANxF)2Q0KK$#lA|cQ=TAc3J9=l9@KwKQHz8wK?b2#ha!Z zbJp;izd7N2zg#Q5x3`{edRdpmKMjke2bt?M{w_XuPix~5)ick`-fK>;438B#JGr5@ zeD>cP7pyX)Sl=gFbR4VxI{VcY$(;FzLe#F>dVeTeYxk~K$dc=WOyANCz3ko%;?pbR zc{>%jKfc;`N^MT{W%in|PpgwwHI;Mj;eL4RB=hG)-?o#co@MQKRF~$svzkWgbkChr z{o%31@sCo<br(_tA3QUPv^*s;^M(2HkXsk4Hm4*njEQ&L|1nr+Q_|B52WKy5OL#b~ zcs74u-ex(=%6*;}BeuuheRikfqsG@{t96$Ds=vI=uit&^SEjXmMb7;eftd3=iMMuc zwK!?$Ft><%m*$mLRRhD;P~GWAe8Mtrn_gX#z1XB+R?Uwo>ov-YQX`v|XPLjK>)PK| z@Pk>;X3<IBMc=EA#@^}r62|(GH~!$QgEJ#m-&^!x(x%CaC6;`1yt=vMLm)%r?vu+_ zybp_3yuHe$_roXuW2Y}g+Enb^bL@2KAy2l9&1pie8znxRXO{3cQ+n{>Z{o+`xnlEw zzeyJ_pR#+;!ZpUOn+raj;9PgY{pXgInqmD*IJd68QO0j_%0<Ix-*K5cw~e<}$^AGT zFQ$J*QCa)&)~%eHiGn*iPbNGGTX@*TE16xxPw2jhdB~2Pf=oH9_y1bg$2@Jv2cxME zmMxibdD{Jr4WZeeigX&*_bfi1xadkcd%9CZB-gP?huk;r@cGdjdP%J2_33S<^8YpW zEjhv9e|?(J=AtL9ZntG0?SFUt%CFF+8z;^UV6Z>;B~J6T?@A56(`sptb>r5#rl{v` zuk@F_a4WZ}!SAL<rkv$M*82I!e(%fGSs$~IO*Z^cgwoty{<5xzY*v2>@)b)hT6V<f z=GBO|YXXd)wyx)7iVIuzLU!fqJ5M}WycIs&YTPKYhQ)Pjtoj}9)%vOt-l5m;Je~A> zi=c%~jN0quqtRDed>m&Nh1_51`MOZU`dx?W0*Co}-;)F%-aMSW+j$yuzH)uVY%h7S z{bKE`<<W0H?1&NmUGs2rhgX7c=CWU_jQJ(Ef3%4{<tsS%U$6=P(T9hvwO{q{?e+Fn z`&A|NF+sPcWS!2_#Tn&=vyXCbKN<9`F#TDru>GsWHSYI+o-3afwQN!T8$k!IHl>Sy zzFB$B-E9-xm9jPJ=(z+Qjb_p5CVM`q27VWD&kJY$_snX^6?fb1FE4VIXRsfAa@$Xs z$2B9}F+;?@_ELD(v=t97E;A^wiL9CRX2+$C`TfiF*_X3EdNk{ZrT_IATQ`<%51-(3 zF(Nei;P$F7vvu6;R_|lG|0BIOQn<VG>k^TThm`wsCsjQQYFj_)Xr|!($UhRR4IH~$ zRqhpsg}K}o<h!vf$4rIQdD4rmt>J&Xw_iE9hW&+{?Z&ic3G<zAEq-73eYffH=QsQ; znT;jix^Ca(vHt23Yc>Jpn`avr`b@gID!<-!;}z4@A(fl0ObdKo<zK!1YO~j)$sBth z#(ZObp7Q&d)l@YN;l$9{7AKcz2P>UyiQKCre2Zo8x;EaNPy7{~Ta6`SI8(P)JaFEx znY-0Hx5u<>a#``(qP?1&u8&Ve#^j1zJW~Dc#e~Ze2LimNZTJ{b@LR>=qx8=Q;<cY2 z$MjC}^8IJpf2`5((Pne*XU~G9k_+~w>pf^XyeB@iTWbAG<BvDrE-i@TePkB7nJxaW z@^-fk|4fda@|}?~m1Rq1_NSRqAIp@58ihq}#0ciSo4k5D&-o?Qk0<@F-zhRBz-JPp zO5?R~sp8e00eP!V8XHW0v~_xSRzXu`;bgmd-PLOB-RE~@{&@DU;dsBNT8XX3s)+YD zZ<TD>vF?^};Ov5f$u^w7BDe2|DH8raqeS>mkmfYcoBmV#G|lU}rxja?>F@U2vBUbu z!*`cwiA*zUY`5vXd_!Z$(S$cT8^07C?U6eoeVtu3`qQ3GQ*@6@-ON37@v_aYd*An6 z&rq85<m;sWe|O(ERa~)n<E`*;&YhaU;nS}Q+vo({k$jlM(Q`x5K$pL7(dMxId)(WZ zf<&fy{y9(@rOW1f@PVqpspDE-GEG+<x~#s1iGR6gda1tNzM~yyZ-ut+Shnx|wxS<T zU*+p+dmlUzz@d20_~(bicf}5vwVW;s+kbN5u?%-@HlgXZv47qbN8FyQY~98ZckT2~ zan&OKeN%c<rGuk$uBLsgdz&2}{inbF-}7gB%-5UBPfX^RbL{@#ndeQ6U;I@)^Ziyt zh`76OEk{WF<h<6d&LeJx&C8}4JoW9I_H^4epFO{qGQD7Czh0QlI^|ul&WDi5fOH#? zgJ$!)Mg9meEnToDaKoD0^&;<_?tNGxeC74?$VDgBVzlM+U%WGZcy;N=T)_hQJI{Sh zJ%7}!R8!w5?0rFGk;u&5&scN3PV^>CI2&>)Z{a4rl-o&XH~q>F`_QYlN8R(;+!)(6 z>fFUT$G#few`kG$VWKYPwnlQj!?gIn%j>g_i(jAe^vzwRxLMjF&M)V9nhNBHU5a5! z-}m+#k5g7b=(9E^=Y>BqzCT#B=AiX#{lX?g3$MmVrX$@4x0;)J7tZ=H-Ajm-_3+<{ zf9{GtGbE0wG1OFS>|MShcYCDpsjr2n>vdPN=jDFfGV$F1gZj6oL?^s=YIcfLsW4OY zeDJa9{q0k)Ce2isrD_&d$oF&R61M0MxAHW~pGzE5+FI~wLUnvywVlQkfkU@Ngt$+A zdZ^oV>)Gzv!UlaNJ3JqDFhA{if9%lZ`|`DqrSGq3t<TxW<2ET+=i`LVqI~faRjSuE zH5ol@xy=$*kWiec^K_-nTNV|z=GjdGdVh4qv}Go3z2jA|O-l6nsSn2I!)D3$Fj{xE z-*%~4#^leHko3j;WpS92%Cb*AqU)QYC+7#+#(p|jz2<Gm{VNZz|L8rv+WN=R8EcqM z=lpr6IfvE3jH#&m-4d==v+wzZ8|zM`Br3a13lu7-HIid{_AP41_q)aZ)_of^jF#)n zKJFpra>>>3XNzdtQ#E#@uL(JpYfBpUa4s*orO3|DmNWkvPxV31K&}trBF+czm$j*e z?ABmjx%P$OJ=H^Q?*A7}{k)zjV(LeS7k3s2=Qll;JL;$NDcdxX-}YC=?W0Yn^R_LF z|FliTbmqc-eJ>TUjVnI*RI>iK(>%40e_cs*RspN_(}UYS+vRK5{Opd;;d&KpJbRMN zi$%4UQr3O&)=RS6xbm#z&-_QzAMFn>SaW6izF$|%qRyZ74%GTwk^AwE_SA2S-`D^D z%^JLWrO>{s%jO*UvMFSWN0ZC$@3ZCBw;yepwM1_6r<jc%Ns6iO*!pHvF^ei}GD*<z zD$P5^emrU?$5t!LMcx)B57kPpc}XALXb_>kLzC(9*;N(SJ5OhN?VJ<wB8>B{UMi3I zl_kaJ-#vV8wqs-Ln&9?#dlqq;M?RXfY)xp|T#d<})pzTC*O~5mTsBp4etXU$rn+nI z*IDVP3UmA1Io`E7AiI12>-F=$+~4=1^;??uVx`o)sb}Z&yB?EOoUvH`&jWVfNmH~= ziL$UAIBOaCJ#UKVnFT&VORG1X+p4kPSKLO9Ihz8Mgb&Q$E*i%iy#AbuWW<c49>Mcg z`R}~Dyl&>}<2{=N{MNn|`uO<Y%uRAF{A${QYmBBd$3I?M)V`L*V%;_!mj1$9Hv<lC zQhD+r=jiMyrFlVJXK%K!7cC7?yK^LB4WFs+j<RG!^_V`^lfO0@J;~TSalw73t9EKO zQ8@?CTiArNZ<r@=pt7z%ndgVn{OZ$NZyxv<VyL0;!7cD;$UM_ys(oMj>wX-T7kl$j z%XGrGZK;aOR6K884OS8M=q$Qv_$#9EE0<iZTDNr5acx7j?F>?vKQ!H~(7k=o{ot3z zW`UzJVMS#nNz2o_*=odn)*SladUHyonOIM2#9I~b4>vb)PrH5iZWHV08SDpC3zBb& zv_Hs?cADQa*Z%2;t14ZwdreLVO}abdgUmhQFRPUwf19vhOxxzy<I6{?r*}{N=*8PQ z>u|r^pJ(6R-D>=O;_&j^4~r*vHx*V-SzF$zy#L+oq_4a!&u49l3p3yU-ey^8oYstk z7Qd6yrut4>{%;D?=H883?FZ+6Qr*)z!N;J{@@m?txBD(^K6Jb5Y>R-)>y7`|&#E|? zh%Nt_s~%Z<o3oNpKq5-4D<Nio+FYwY2RCQ)vClFOTX6pA&g}}mW|3zjCrorZ?An=i z>8Q8PM^RPbO$B);YNIYZOB8QCC3CamPP}|cMVQCA7am2^jg8L>L|$Jh@?(qa-xu!o zSrW(CdNu}qvJ158N?rQp8aGd|_P0+uy3Z_oQjZoIKfkIw^LU4u$pOip>iNN*O;1*3 ztlwtR%^Bx>b?chHw)^bYc^)m~3lAzzH`R>MObkjm^(*J7UyN^cM;y~Ouk_&d;uhz{ zN2^WwxUMG63A(X-n)T%!uQuLa_kE+0&Klv0X)Y2EQW*-D6dYE2S|eTIbMTSUYyJ9v zpS!Ey?c4p)eceoXOI9)Wvpe@{*7eu@c`P6PznJO$>6Wuaitk<p=kL9xBJNz&b)w2P zO5Uo2^U?g-Nuqo|VzpgQ|J}sP=V&xz8~0a(t)~hePCB$}df3m8XLl`a+U_{__v9O= zUhhv5>w4lK(9g%ZGotImORHG{>B)-AvI@_cu|`iiC@i_<$Fg~=E!{qTYrg!N<@clI zmzQn((Iuv>p|jw~qu4Dc&o?e#5dWvUjQdwM&lB|}2HU@f@*I6@Y4kE#HQ9jwPTaks z1Nl$(a=-W8$aJIJ$I|O+^m>77&l0yBxYw|0m*LWT(Tn)F?|T2*xhUk|T6y7nk2W-I z-gH!!&0jLAzIM7rQ?{=CzR2)psq@87OgmIi7$fh<eP(UebGH}m?q4E=!X`~koT}OO zeZ~P6zMXcdI-U3Dya`rmKOJ*VaSEqx$%hw>+buZWXZ$G9{WCFIta)<XsZ_zjsdsk! zo_TKnedjg#7~fN~a&B*QIaslcJH2F~{Q6JJs&d`!f1dQeb%-zE)5gbBzH;e2w7#;q zhWBL=(-*-V_F?fiBt#Uq$cm?~G~!NV^4Y_<sYFe=;;^%b*4Ns7DgWj;l{y(Qs?A!U z!O1FUFQC_WG}e4syM2XFH0QKPd9VCAa&num#k8a>>Rq{W(IUkNx9QeP4;<U-De3!S z-N_^sf3ByM^Pb3B%ofYc3TCW+y#80!^7uDTOYUgyJKK5Xw9x!V8U{c7qM7eJ?yu86 z`l>TWD?PNY(Y7LYW8A43Jq~Ln)~j^KbF>@H)R^X|7vLxFvv%E~+icT!-O>MSx|AvS ztB(4@8=72sQ&~5DsPNZntoRi+{iciOk_e^3`5%8znpypELS)Wir9xZ(X|K)lx?dbw zS>fB|R9Gpu^nh-F`|%muBAsj|9o>6RY;E`KDYIgxTgL6Q<w~u|ShAOK{)6)u{p){S zUfOe>SIuMc;oH+S+Zp2QwsAg)Tt6o_@zPb1^}lR__QxNns$3G}5k8IWU+?uvbrl>j z8N1CH->zBHlHt0^&vl2b>IFN~i@UYNt$w5*edBdBL5(SHcgnQKN0(~P+9dPq{p<+G z?mnldj2q{^UdJU`XCLjjbNbf^?LEDl7JHb_z4X?%H}|XRmXp&3`i}WsZ9KTZBI8ui z&2`ZS*G!+QTq6`Cr&_bnujp-*+?x4+HqLq^CmrxNa^?3)^N(E>Vt38_qx;auq+HA4 z9CLzo5PR=gtE*lynjsG(9>xhZ@80|Mft|^;+}dkv+<Z-z7P%bcin5tx7F{sAyJpL- zMK_giZuD7rLUsLy2ZqxG`m_5NHEn(@Yae6QD&k%_zt*N-$=T*v`JPALjKUpnKfl<z z&#y9g{?+Lh7-s~pQec;Uy2)r!!oh{Q@eMN=n0=F`<-TW~T-GPI@r!WbntQ#EuPxPd zp0ehJ(45mM-PuR{r$nqtY~8%*!_B)hJEj_m_FXkTW)NH%toJ=bq-OpO$H&>Xf6mg# zYiF_zGE_b~HOK11$C*|u*<M>}#rcHUuakS9vNOZHJL1P2^@;qgRa^S<KR7OvzC5Y? z<I2O=fA9Y^bzAV9uEl32&W*EUZ8rYAP4K^ub4e15<*%FP>%uC|bDBJTVzp$>p7p_E z*IjlVGZW5z6wA^-G3L&*<fSoXeSBP;{JkG9^<SEy!|$RwSyt{~xA`2)m%D?6EDV&s zJ}ujwm3j8(=2f~CTUfp&ne0z(wX^Dz>oMJONqmo0<38cPZ`k5j+Er`{yg8#wj{E4N zd!fgLbqcKc*UY%`FyD;x_AhScFW)87?woa<cW0iv;VtFYg8BBxlW*I7_uW|XLeOvO z_N%TteVSJL|LWQ)<Gms7Vv%Kp*HdM0u4$p``lo7buR6+c%jTG1&up98tF7BZ>ueV6 zo#Gw;KEHBli?opQ#;S9h%6hYZ%~g52d`_RSneF5)yGtBS-wM>}zC7KyGI&ws#kdL1 zH-DV`{IB|b?)uFEGvee991Ku;*tyz1RQ|%*dv4n{JH$S|c)tGM=U1Df)<5?Zy0Jz5 zen`529@C->&zH;lik?O|Rj(0e{Q2nl!e3`K8f~X*mThcSk<hz(x>{tFX8gyV*AJ#` z4=l0#lU=GcZRLA+#Uq(=H;Uwc6rZ|(FG#oMc3IrPnRl8JR@$h2{2-MlEtVf$u`)pV zbc*nV@Q9wdt;Jh5b~^=n-B+ml`0w{^cD}`jQfkCFT7`tYSLU0u|JckKAG~H_0{@&( zGrreenr8J*f1P1&?Ygt;jIL>)T58W)^zLZa(WC-Sk!h?A>ReYJ$4u>?X%WA0QNb<k znKd={BATA>^Wiya{!?uR_acVno=J{e3J=Od8rtsLP1TWly!^m!tq|UQx;ln)EzU&C z26H)w6;_L6pL**Ww_Ni6vV8^XMYJ6hXT;Q=I=TGSF|%gnkdIBAZ+>g^Ut3+?pClF^ z_<v(v$C-9^5uSghty1r~(<>sL9(U=VniJVq{q&v9sRY?0_4n$zboZEeFYgc8;x{?M z<<k!*&1U}<CysJY=F8ffo?3I3OTTlD``Q<quhey)c3;uCxFLOw=HbUSr#|n!*rj_h z|4dbkbYoeL#jm6Kbr+9aRQ9#BjL}q|ze@V{9pQ}kQtlGdP9F){7q|04dCbS@U3>rN zi8Zd(30oebc9)?ke4osPC_RT~X&NHgia+j)*{}62czD-5QsG#MdoF`bouS+Q3qBWf zoOWm!CYMcG+_W%VaYN3OPy4!-zS*X{yzWfG)<BJSdvyDRKk)d^cUrvu_R>8;ItOeg ztt@&q>3eO^^ga2?7k0RHnW<?<yqW48`j+LfQb>QR+`8sa`QB|3OKx4vyuN9T|Es9V zO8<o{Ct?D-(@!~Z{JX9GvDNv{mCtMP=U=;$dAxH`z@!;#_nL<#e+!=0^h0OD=dwwu zH}~>Ac|QHqHD=a5`=%VNoHD=Xjc!1N+WCJICf}ZX>0aKs2j|WHxbOe-RG<6VoFCh! zUY@r{-sRP#@3|}nQS0mW+)COzWpPB~!WrlG{)=H>{bBY3x8<jHg!A1Mvlr#t|68+e z6QjU{>J5LrG!0Ul_WQrQeSK+-pH=APlUi?2UOf8yZR)~zI<5}R0eRt__ceSTJe|^R zWU=bQk%mK-w+!z6etkVLV%Hq^rSZpB&6hZJa%b#|I}%^+UX%Nyezy9L@5ZJT`5u;* zGo~D`xo%dFwECXuD(|N0t*3U!y;{srH0}K7W9+Zm6KdyLN9j+_Dw55xnm55mr2Bb@ z&4;y#D<`bo^JjA5*HX>(n$u7D9b9x`%3<atz1g$B9?tmj?9nz=u46^j&6hKGvL4*( zX&Zk^Z@oxmf=-3|;`W<vtMlzM_sbm*sr>5Uo_s#x)XCr9uBhJM$~(u|PkTa9AWQgs z8zH8{RXcfXO4bC}N9w+)Te5FQ<r1A4cNdpU7tKG;a;`a7|B9zh>)TpI>%QF~VtamX zH3{9$cHyo_PVkeU7_O%Zp`U(jleeFIO=Ra)pZ^PIh~1g~>P7jl2Ui#vxI;i|0UpfT z{XS1@8qY7!@Uo3Qk5>Gfu(^HqvJE?@-8ohwRdcZP%j)>Qt30>6n}zzt7I6g0eO|e3 zpP!|dY1H}$Ov&yit0GSZwJ2PVkNGCQ|9@?<@gw!x+sqF)iJcP3vfNS3mD(h=?bqe$ z(@PuqJ?1Z$za?_;<eu1B-``ZX-uik+o9pRj{k;61#ZI>)b_DI->zlWJdh{<@qx<6j zy*3(LET5;fT(9!Jdg;mKw|?!KB79@^iY0RGo+&$%9#%aKI@kH{{lB;OZ&gkZ{~=WG z>UnI>MLYL5a~A2ouKL9|`8nhI_3iJqPHR@K`Ql-FesWo;#vcp24|$@kkG=`Hf87!+ zHm5paj%?y9>5DV%cON>tU`Fn&!}VWY$N%4TYqQw$soAS)*X6C_U0%Dp+RN4PiHN6E zV%2NWr%cOvu0>DzS3KSGU2MRIHJ_57WS^R1<{KpP@yELv;{5ZI7alQ<OZ@0RQ->#S z^-{mSm$#pP=u+I7y8qgw$IJ%G{7)2g9(-eYu{it9$9KVUFW+1)7r*h~{*N+dm6=<f z%zou$P;fH%{&(rq?~b~J<*b=}QmRnp+aoE>#Q6uh3T^BRIm`Owd^X3mo>(*Ybm^L1 z|9sg4kL_F<diLG)zcrC>FP;p4^*V*?>Hp7v-2Y1N+}_RpBiw825$)_RQ4jCTfA%da zCVtgoX8ZFeeo1Vfa?)ew(XSgU^7Oxcx~#V8Rj~i=_4b*TKV~20nl$f3Naeb``(`G~ ze+6i*>z{PAs&)UD!jc1~U3+;_OlN4le!p?T+{etN-%2YZ+qWDSTKm*7?v(h`L(|WH znOpLlaiefiyh7#oxkn|Z8fJw3yj117aL3;B({De%^y~X#Ci%#M>VH=~%UUwue?IW7 zv|UG@>Hn;x#C7hoiazZFh56d!8&~=))Zk!!B&X@tA8h}2@7JzR8Nqq$=S@-iQ5efU zna}fl@Cv_~Uz`s&u4+HFn*GSqfVz!4G(#d+%=3Hw_`J)fLvq>AV?XOpnObzpFLTnb zj}rO&Z+AapEsMVQ_!e9BrEj&FGJl_$?_X&bx%zm@@844<H+xi`DbJnV6s*p=ylBmJ zwWmy{i;f=pI(eSa>#xs)*4>}Z`q_)eJlG}dnr14O>Au1T>4vwNA1!S?bUEbJ(suPi zk*8<&s}(-G($CHJUGGoD%Xb&@@|Tz&{hs-1+e3T(V@_w5^vFJcRhB!u`Lsf{gki@H zwJzhMlgqSac(hGF=|B6G^*5+%?sfTpUzUHHDe_u0{F$r{`;2?M-^H5l-a1_B_N?XJ z_x8B^olCj<ljUN>m{|XM)iTWD7vJRk_P0d4_U^qOkM5ECwj{Mk=Ca2AjkWWC=l%$H zKlHcd8_Ta>QzkciSDsmZYunY#=}q1<iz+kD#P+1e-BLY&f#u?@*o21@ro0t;zm|Om z(<A$<(!%v?*l#DzUl#E7*wPtoLI0LE{rkq7@Gz^f{&2vbka-Dpkz1#IEu3;a^4TMe z$=)~rc<6Xv-M#k%&rijv3TM9kIN#*fzklZ;0lChv&EA%k{MO;-*RMUEp`AT>#}VIC zr96du=6>JCQohCPLXgS#SGHnBH(i5l&VQEE-u`^P!`$au(>uc7trJl`dE-c+uEBbX zQ^k9a1peJ|aJPH>nx_jUFTHEtYw`W<e%^l*e)QM<zI|V8U-2QQJ^SZtoxl3__BQUl zaSDNYTn}e?y;{B|e0@gQl>V#A3;%!B|9{Ev#oq(D`mJ>`3oqXC4*PT@@TX6#jB4-S zBL9arw;#{?IPurDmo4>tjmgq4CttcDmF&xzxGwMdHo3g%MJxxN<`(y^@k`RbrhQzf zrDfWY+Iz+4ZM*+RPJ8SxdgJ%{<SE*3ZT%Oneg1ptmL$#dUu9e^#Ab9Y{<82vjOFzc zK~K-TUNTwKZc_X8JMvulA5QvvC1>}T3Q5kKKd0eo*PLwb%LlI?&eLia7k+u_aE`F7 zP2<Gp0aL#gl`tL?{iAkEdhVJI_8a@xKlu0ekZOkfWQ$YM9REHy-+%Ze>Vujqr+$)E zxZHJ?^cT<n1z7H><&v+xn|XTorHIK1_78HO?z25}?&{avTFctwd)i-~ww=?NI+NvI zOz?ZD%jyU1xsO&AE)CS%G3DWp_g{7AU9RTe`uBp=^{0wvb1eI_-p{<QEXFl^-`Wd* z8~x*tvnOeOS*|8gpFFEcI`P}i{}vf3JlS2QMsrU}zAm2s?a9jN^OxkU@-N8CGA>k` z^vI$pVeh0bo{M-A4pykRFl2^bn<o6eT6@BO`~Uymzde32Vzd7B>$mMTm6}ZzVSKi) zyn0PYS+#P^gXNz-Ox{-eYuWj2f7WEO{J-@(V#Xx{(*^tX>8C5K^q%+2*P?ny?5Wuw zTKv|8_en2XBQ9YmsCwp|P_AZj+FrYGYny^Nfiv9YwKAeN_QmsAetm!Z@`U*x{42b_ z&&zw|H+#NlZ)A7t%Q%l(OXgXtXJ<<nvfq!ee<bm~x}t0Hfx5|GFWHOrWuIZr{W|Gn zaN1st)4!i4y=5|9dGcUTT$jjp_n+sCT903NQR#U%^Rx5Cj4sy5^G_=5kG>H(6BB#? z+Vtk?Re8a;MRHy}xigDXeyaA59#NjvQ-Z6#7OfYF-0__+?A7sqG4qsJ^2}<PjIXNa zwx6;;X%Z&JJ$Z3~Qo~L&gW$L7D_{Tq@>EkQxjNxVpv<YGPkVPC`!e<R#*HNpa+t2< zdKIpJw66BP`TLJ~lRm5M+I{W2$rnp*tKF9(G_ReoVF~W}`nA^buYb_>?>9r*_UF&u zC0{8Kt9<mtj0cnMbniUytaOe>z=y`dz(YH1#q_^6-Ztfbd*E=f*vrKWHg_JImBe_< z<it)hr<qyzx90h+2)3yD;rn*w^kAlq;-_6JtBzjOI~loS$FV)>{&K&%x670qdz(8c z`26j4Pd^C!aDK$+SLy$JP39WSu=6ewGm{vagTKDCzq)Vjy8QR256-MvmAfkMiGBMj zoy#Y7SAUdfU2HL>o2lyQvBgsSJ(|m=?8{JFs`q{Q+3Gt5vQqYQ(tVS|HX3Zu&7F1S z%j~TAtGH`E$t}~*mVa-4b!ptZlNYa>F4ORPbvyIVC&M#SjM)6{J?5Uf@9&nUn~&M| z9sHYlyl`HU<Po(cwbAi?50<^Uq&7e6{p(*v_w!f17kLvB`{B&i#+UtEM*nJJ=lp&! z>-0s>Tg&5r<(#hmlW@L?ZO`(ho4-CtRcUxJV_CwoSHJ3Nf9s{zR_|E(+qd-9(vANM z-~OyWxYN%%^WbLfyPqv@#$CSrW5tV{=c~kC={fv9;KRFa{p-`aAKYcLziJmM9(C*4 zEoSxm?AzX(hw`m^5^T`Eg1hc#<kES^)U2wt!}?d~`|~Ev|0cIhzrSMnt+KVR3w^)7 zy#3qgMojJB%u@%q^UMFf^g6TVN{O0ic1qHo`BD$eX5_vNwg@)=Ci|Y*{MqdXTk@M{ z9@?2dU+{Q^yd7Wf_s2_&S4Jp1|Gt+m9B_Z1ccu6*+j(+z!GHG|*zPgfv-8-t%Xiq* zKV5z>>$UWslg|_UFNN*jQCzl9!nysB{Ugcu&odh5Hoa%v#_hv@f4cwW?Z=Hn`QGLJ z@&9^v`MV1xekmJ&IevSZ%ltC&yYAt;-)HW+AF@17>bLy$b^q_OrtLkx)4H~J?rPf@ z%^j2F`951Md*HwC-mcqsS(o1p`krDi>B{ur@<}z)^HzRax$DBb?AM89jog1i*43|D z`OWzX>;KHkyT?v@zL;|&DK|MK=RNzq>Fzu3oIiMG>(c!-e0l3~k8IS=xu36dcw%;r z^_AKW@=f<G=T%u1Z?O|Pdh1BW`G|PAhsU?&et&6yXlb0?^=pS&%jAD-tgq+yU$^hy zgr^Vu>pbo#ue&he`^)6eHS;EJv@x>U*8hF>1M{n|?XR0X;{UU+cG-s|eCuLXT6f!+ z@SXfEGT(M?vv%0|Kg&Le1iz~KA()mU{r>qc|ATD%x9$9P;OkG%Utj(%KXq`YzJBnI gXRqwEtp1BXi@UE8dHKu~1_lNOPgg&ebxsLQ0Ok7f_y7O^ literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_floor/tiles_60x30.py b/archipack/presets/archipack_floor/tiles_60x30.py new file mode 100644 index 000000000..f8b66129f --- /dev/null +++ b/archipack/presets/archipack_floor/tiles_60x30.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.b_width = 0.20000000298023224 +d.width_vary = 50.0 +d.t_width_s = 0.20000000298023224 +d.is_grout = True +d.tile_types = '1' +d.space_l = 0.004999999888241291 +d.is_length_vary = False +d.hb_direction = '1' +d.offset_vary = 50.0 +d.offset = 50.0 +d.spacing = 0.004999999888241291 +d.thickness = 0.10000000149011612 +d.bevel_res = 1 +d.is_offset = False +d.grout_depth = 0.0010000003967434168 +d.t_width = 0.30000001192092896 +d.is_ran_thickness = False +d.is_mat_vary = False +d.is_random_offset = False +d.space_w = 0.004999999888241291 +d.is_bevel = True +d.ran_thickness = 50.0 +d.max_boards = 2 +d.t_length = 0.6000000238418579 +d.b_length_s = 2.0 +d.bevel_amo = 0.001500000013038516 +d.is_width_vary = False +d.num_boards = 4 +d.length_vary = 50.0 +d.b_length = 0.800000011920929 +d.mat_vary = 1 diff --git a/archipack/presets/archipack_floor/tiles_hex_10x10.png b/archipack/presets/archipack_floor/tiles_hex_10x10.png new file mode 100644 index 0000000000000000000000000000000000000000..4d4c8ecf612c11735572e97f4ba3daee0fe1ed0c GIT binary patch literal 13663 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 N!PC{xWt~$(698@$4?X|@ literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_floor/tiles_hex_10x10.py b/archipack/presets/archipack_floor/tiles_hex_10x10.py new file mode 100644 index 000000000..01086dc8b --- /dev/null +++ b/archipack/presets/archipack_floor/tiles_hex_10x10.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.bevel_res = 1 +d.b_width = 0.20000000298023224 +d.is_bevel = True +d.hb_direction = '1' +d.is_width_vary = False +d.b_length = 0.800000011920929 +d.spacing = 0.004999999888241291 +d.is_grout = True +d.num_boards = 4 +d.is_length_vary = False +d.thickness = 0.10000000149011612 +d.is_ran_thickness = False +d.is_random_offset = False +d.offset_vary = 50.0 +d.is_mat_vary = False +d.tile_types = '4' +d.length_vary = 50.0 +d.space_w = 0.004999999888241291 +d.ran_thickness = 50.0 +d.max_boards = 2 +d.t_width_s = 0.10000000149011612 +d.t_width = 0.30000001192092896 +d.t_length = 0.30000001192092896 +d.width_vary = 50.0 +d.mat_vary = 1 +d.grout_depth = 0.0010000003967434168 +d.is_offset = False +d.space_l = 0.004999999888241291 +d.bevel_amo = 0.001500000013038516 +d.offset = 50.0 +d.b_length_s = 2.0 diff --git a/archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.png b/archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.png new file mode 100644 index 0000000000000000000000000000000000000000..07c6e266b92570941291cc0bdf12563f38fcc9b2 GIT binary patch literal 12511 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 Zrxirpp5e3UI0FL%gQu&X%Q~loCIH%t3oZZv literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.py b/archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.py new file mode 100644 index 000000000..3ee45a2d4 --- /dev/null +++ b/archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.bevel_res = 1 +d.b_width = 0.20000000298023224 +d.is_bevel = True +d.hb_direction = '1' +d.is_width_vary = False +d.b_length = 0.800000011920929 +d.spacing = 0.004999999888241291 +d.is_grout = True +d.num_boards = 4 +d.is_length_vary = False +d.thickness = 0.10000000149011612 +d.is_ran_thickness = False +d.is_random_offset = False +d.offset_vary = 50.0 +d.is_mat_vary = False +d.tile_types = '3' +d.length_vary = 50.0 +d.space_w = 0.004999999888241291 +d.ran_thickness = 50.0 +d.max_boards = 2 +d.t_width_s = 0.20000000298023224 +d.t_width = 0.30000001192092896 +d.t_length = 0.30000001192092896 +d.width_vary = 50.0 +d.mat_vary = 1 +d.grout_depth = 0.0010000003967434168 +d.is_offset = False +d.space_l = 0.004999999888241291 +d.bevel_amo = 0.001500000013038516 +d.offset = 50.0 +d.b_length_s = 2.0 diff --git a/archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.png b/archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.png new file mode 100644 index 0000000000000000000000000000000000000000..33d286573abb0ff831ffb30ba7d3dad7e2616bff GIT binary patch literal 11631 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 bKk-lY7zEtBTi?vUz`)??>gTe~DWM4fIrEv` literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.py b/archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.py new file mode 100644 index 000000000..8f4253fe6 --- /dev/null +++ b/archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.b_width = 0.20000000298023224 +d.width_vary = 50.0 +d.t_width_s = 0.20000000298023224 +d.is_grout = True +d.tile_types = '2' +d.space_l = 0.004999999888241291 +d.is_length_vary = False +d.hb_direction = '1' +d.offset_vary = 50.0 +d.offset = 50.0 +d.spacing = 0.004999999888241291 +d.thickness = 0.10000000149011612 +d.bevel_res = 1 +d.is_offset = False +d.grout_depth = 0.0010000003967434168 +d.t_width = 0.30000001192092896 +d.is_ran_thickness = False +d.is_mat_vary = False +d.is_random_offset = False +d.space_w = 0.004999999888241291 +d.is_bevel = True +d.ran_thickness = 50.0 +d.max_boards = 2 +d.t_length = 0.30000001192092896 +d.b_length_s = 2.0 +d.bevel_amo = 0.001500000013038516 +d.is_width_vary = False +d.num_boards = 4 +d.length_vary = 50.0 +d.b_length = 0.800000011920929 +d.mat_vary = 1 diff --git a/archipack/presets/archipack_stair/i_wood_over_concrete.png b/archipack/presets/archipack_stair/i_wood_over_concrete.png new file mode 100644 index 0000000000000000000000000000000000000000..9fb3d56cff3a4de9748f50e359b8b67d9740888c GIT binary patch literal 15606 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 literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_stair/i_wood_over_concrete.py b/archipack/presets/archipack_stair/i_wood_over_concrete.py new file mode 100644 index 000000000..53b605cfb --- /dev/null +++ b/archipack/presets/archipack_stair/i_wood_over_concrete.py @@ -0,0 +1,117 @@ +import bpy +d = bpy.context.active_object.data.archipack_stair[0] + +d.steps_type = 'CLOSED' +d.handrail_slice_right = True +d.total_angle = 6.2831854820251465 +d.user_defined_subs_enable = True +d.string_z = 0.30000001192092896 +d.nose_z = 0.029999999329447746 +d.user_defined_subs = '' +d.idmat_step_side = '3' +d.handrail_x = 0.03999999910593033 +d.right_post = True +d.left_post = True +d.width = 1.5 +d.subs_offset_x = 0.0 +d.rail_mat.clear() +item_sub_1 = d.rail_mat.add() +item_sub_1.name = '' +item_sub_1.index = '4' +d.step_depth = 0.30000001192092896 +d.rail_z = (0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.right_subs = False +d.left_panel = True +d.idmat_handrail = '3' +d.da = 3.1415927410125732 +d.post_alt = 0.0 +d.left_subs = False +d.n_parts = 1 +d.user_defined_post_enable = True +d.handrail_slice_left = True +d.handrail_profil = 'SQUARE' +d.handrail_expand = False +d.panel_alt = 0.25 +d.post_expand = False +d.subs_z = 1.0 +d.rail_alt = (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0) +d.panel_dist = 0.05000000074505806 +d.panel_expand = False +d.x_offset = 0.0 +d.subs_expand = False +d.idmat_post = '4' +d.left_string = False +d.string_alt = -0.03999999910593033 +d.handrail_y = 0.03999999910593033 +d.radius = 1.0 +d.string_expand = False +d.post_z = 1.0 +d.idmat_top = '3' +d.idmat_bottom = '1' +d.parts.clear() +item_sub_1 = d.parts.add() +item_sub_1.name = '' +item_sub_1.manipulators.clear() +item_sub_2 = item_sub_1.manipulators.add() +item_sub_2.name = '' +item_sub_2.p0 = (0.0, 0.0, 2.700000047683716) +item_sub_2.prop1_name = 'length' +item_sub_2.p2 = (-1.0, 0.0, 0.0) +item_sub_2.normal = (0.0, 0.0, 1.0) +item_sub_2.pts_mode = 'SIZE' +item_sub_2.p1 = (0.0, 4.0, 2.700000047683716) +item_sub_2.prop2_name = '' +item_sub_2.type_key = 'SIZE' +item_sub_1.right_shape = 'RECTANGLE' +item_sub_1.radius = 0.699999988079071 +item_sub_1.type = 'S_STAIR' +item_sub_1.length = 4.0 +item_sub_1.left_shape = 'RECTANGLE' +item_sub_1.da = 1.5707963705062866 +d.subs_bottom = 'STEP' +d.user_defined_post = '' +d.panel_offset_x = 0.0 +d.idmat_side = '1' +d.right_string = False +d.idmat_raise = '1' +d.left_rail = False +d.parts_expand = False +d.panel_z = 0.6000000238418579 +d.bottom_z = 0.029999999329447746 +d.z_mode = 'STANDARD' +d.panel_x = 0.009999999776482582 +d.post_x = 0.03999999910593033 +d.presets = 'STAIR_I' +d.steps_expand = True +d.subs_x = 0.019999999552965164 +d.subs_spacing = 0.10000000149011612 +d.left_handrail = True +d.handrail_offset = 0.0 +d.right_rail = False +d.idmat_panel = '5' +d.post_offset_x = 0.019999999552965164 +d.idmat_step_front = '3' +d.rail_n = 1 +d.string_offset = 0.0 +d.subs_y = 0.019999999552965164 +d.handrail_alt = 1.0 +d.post_corners = False +d.rail_expand = False +d.rail_offset = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +d.rail_x = (0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.left_shape = 'RECTANGLE' +d.nose_y = 0.019999999552965164 +d.nose_type = 'STRAIGHT' +d.handrail_extend = 0.10000000149011612 +d.idmat_string = '3' +d.post_y = 0.03999999910593033 +d.subs_alt = 0.0 +d.right_handrail = True +d.idmats_expand = False +d.right_shape = 'RECTANGLE' +d.idmat_subs = '4' +d.handrail_radius = 0.019999999552965164 +d.right_panel = True +d.post_spacing = 1.0 +d.string_x = 0.019999999552965164 +d.height = 2.700000047683716 diff --git a/archipack/presets/archipack_stair/l_wood_over_concrete.png b/archipack/presets/archipack_stair/l_wood_over_concrete.png new file mode 100644 index 0000000000000000000000000000000000000000..0e2ce6b66e6997848908e6b15aae6ceabc4ea602 GIT binary patch literal 18279 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 literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_stair/l_wood_over_concrete.py b/archipack/presets/archipack_stair/l_wood_over_concrete.py new file mode 100644 index 000000000..d4fc1344a --- /dev/null +++ b/archipack/presets/archipack_stair/l_wood_over_concrete.py @@ -0,0 +1,155 @@ +import bpy +d = bpy.context.active_object.data.archipack_stair[0] + +d.steps_type = 'CLOSED' +d.handrail_slice_right = True +d.total_angle = 6.2831854820251465 +d.user_defined_subs_enable = True +d.string_z = 0.30000001192092896 +d.nose_z = 0.029999999329447746 +d.user_defined_subs = '' +d.idmat_step_side = '3' +d.handrail_x = 0.03999999910593033 +d.right_post = True +d.left_post = True +d.width = 1.5 +d.subs_offset_x = 0.0 +d.rail_mat.clear() +item_sub_1 = d.rail_mat.add() +item_sub_1.name = '' +item_sub_1.index = '4' +d.step_depth = 0.30000001192092896 +d.rail_z = (0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.right_subs = False +d.left_panel = True +d.idmat_handrail = '3' +d.da = 1.5707963705062866 +d.post_alt = 0.0 +d.left_subs = False +d.n_parts = 3 +d.user_defined_post_enable = True +d.handrail_slice_left = True +d.handrail_profil = 'SQUARE' +d.handrail_expand = False +d.panel_alt = 0.25 +d.post_expand = False +d.subs_z = 1.0 +d.rail_alt = (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0) +d.panel_dist = 0.05000000074505806 +d.panel_expand = False +d.x_offset = 0.0 +d.subs_expand = False +d.idmat_post = '4' +d.left_string = False +d.string_alt = -0.03999999910593033 +d.handrail_y = 0.03999999910593033 +d.radius = 1.0 +d.string_expand = False +d.post_z = 1.0 +d.idmat_top = '3' +d.idmat_bottom = '1' +d.parts.clear() +item_sub_1 = d.parts.add() +item_sub_1.name = '' +item_sub_1.manipulators.clear() +item_sub_2 = item_sub_1.manipulators.add() +item_sub_2.name = '' +item_sub_2.p0 = (0.0, 0.0, 1.4040000438690186) +item_sub_2.prop1_name = 'length' +item_sub_2.p2 = (1.0, 0.0, 0.0) +item_sub_2.normal = (0.0, 0.0, 1.0) +item_sub_2.pts_mode = 'SIZE' +item_sub_2.p1 = (0.0, 4.0, 1.4040000438690186) +item_sub_2.prop2_name = '' +item_sub_2.type_key = 'SIZE' +item_sub_1.right_shape = 'RECTANGLE' +item_sub_1.radius = 0.699999988079071 +item_sub_1.type = 'S_STAIR' +item_sub_1.length = 4.0 +item_sub_1.left_shape = 'RECTANGLE' +item_sub_1.da = 1.5707963705062866 +item_sub_1 = d.parts.add() +item_sub_1.name = '' +item_sub_1.manipulators.clear() +item_sub_2 = item_sub_1.manipulators.add() +item_sub_2.name = '' +item_sub_2.p0 = (-1.0, 4.0, 1.944000005722046) +item_sub_2.prop1_name = 'da' +item_sub_2.p2 = (0.0, 1.0, 0.0) +item_sub_2.normal = (0.0, 0.0, 1.0) +item_sub_2.pts_mode = 'RADIUS' +item_sub_2.p1 = (1.0, 0.0, 0.0) +item_sub_2.prop2_name = 'radius' +item_sub_2.type_key = 'ARC_ANGLE_RADIUS' +item_sub_1.right_shape = 'RECTANGLE' +item_sub_1.radius = 0.699999988079071 +item_sub_1.type = 'C_STAIR' +item_sub_1.length = 2.0 +item_sub_1.left_shape = 'RECTANGLE' +item_sub_1.da = 1.5707963705062866 +item_sub_1 = d.parts.add() +item_sub_1.name = '' +item_sub_1.manipulators.clear() +item_sub_2 = item_sub_1.manipulators.add() +item_sub_2.name = '' +item_sub_2.p0 = (-1.0, 5.0, 2.700000047683716) +item_sub_2.prop1_name = 'length' +item_sub_2.p2 = (1.0, 0.0, 0.0) +item_sub_2.normal = (0.0, 0.0, 1.0) +item_sub_2.pts_mode = 'SIZE' +item_sub_2.p1 = (-3.0, 5.0, 2.700000047683716) +item_sub_2.prop2_name = '' +item_sub_2.type_key = 'SIZE' +item_sub_1.right_shape = 'RECTANGLE' +item_sub_1.radius = 0.699999988079071 +item_sub_1.type = 'S_STAIR' +item_sub_1.length = 2.0 +item_sub_1.left_shape = 'RECTANGLE' +item_sub_1.da = 1.5707963705062866 +d.subs_bottom = 'STEP' +d.user_defined_post = '' +d.panel_offset_x = 0.0 +d.idmat_side = '1' +d.right_string = False +d.idmat_raise = '1' +d.left_rail = False +d.parts_expand = False +d.panel_z = 0.6000000238418579 +d.bottom_z = 0.029999999329447746 +d.z_mode = 'STANDARD' +d.panel_x = 0.009999999776482582 +d.post_x = 0.03999999910593033 +d.presets = 'STAIR_L' +d.steps_expand = True +d.subs_x = 0.019999999552965164 +d.subs_spacing = 0.10000000149011612 +d.left_handrail = True +d.handrail_offset = 0.0 +d.right_rail = False +d.idmat_panel = '5' +d.post_offset_x = 0.019999999552965164 +d.idmat_step_front = '3' +d.rail_n = 1 +d.string_offset = 0.0 +d.subs_y = 0.019999999552965164 +d.handrail_alt = 1.0 +d.post_corners = False +d.rail_expand = False +d.rail_offset = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +d.rail_x = (0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.left_shape = 'RECTANGLE' +d.nose_y = 0.019999999552965164 +d.nose_type = 'STRAIGHT' +d.handrail_extend = 0.10000000149011612 +d.idmat_string = '3' +d.post_y = 0.03999999910593033 +d.subs_alt = 0.0 +d.right_handrail = True +d.idmats_expand = False +d.right_shape = 'RECTANGLE' +d.idmat_subs = '4' +d.handrail_radius = 0.019999999552965164 +d.right_panel = True +d.post_spacing = 1.0 +d.string_x = 0.019999999552965164 +d.height = 2.700000047683716 diff --git a/archipack/presets/archipack_stair/o_wood_over_concrete.png b/archipack/presets/archipack_stair/o_wood_over_concrete.png new file mode 100644 index 0000000000000000000000000000000000000000..215d42b929fed4d739913e0195d8f159956271dd GIT binary patch literal 13886 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 P7#KWV{an^LB{Ts5pu5xW literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_stair/o_wood_over_concrete.py b/archipack/presets/archipack_stair/o_wood_over_concrete.py new file mode 100644 index 000000000..586aa9901 --- /dev/null +++ b/archipack/presets/archipack_stair/o_wood_over_concrete.py @@ -0,0 +1,136 @@ +import bpy +d = bpy.context.active_object.data.archipack_stair[0] + +d.steps_type = 'CLOSED' +d.handrail_slice_right = True +d.total_angle = 6.2831854820251465 +d.user_defined_subs_enable = True +d.string_z = 0.30000001192092896 +d.nose_z = 0.029999999329447746 +d.user_defined_subs = '' +d.idmat_step_side = '3' +d.handrail_x = 0.03999999910593033 +d.right_post = True +d.left_post = True +d.width = 1.5 +d.subs_offset_x = 0.0 +d.rail_mat.clear() +item_sub_1 = d.rail_mat.add() +item_sub_1.name = '' +item_sub_1.index = '4' +d.step_depth = 0.30000001192092896 +d.rail_z = (0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.right_subs = False +d.left_panel = True +d.idmat_handrail = '3' +d.da = 3.1415927410125732 +d.post_alt = 0.0 +d.left_subs = False +d.n_parts = 2 +d.user_defined_post_enable = True +d.handrail_slice_left = True +d.handrail_profil = 'SQUARE' +d.handrail_expand = False +d.panel_alt = 0.25 +d.post_expand = False +d.subs_z = 1.0 +d.rail_alt = (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0) +d.panel_dist = 0.05000000074505806 +d.panel_expand = False +d.x_offset = 0.0 +d.subs_expand = False +d.idmat_post = '4' +d.left_string = False +d.string_alt = -0.03999999910593033 +d.handrail_y = 0.03999999910593033 +d.radius = 1.0 +d.string_expand = False +d.post_z = 1.0 +d.idmat_top = '3' +d.idmat_bottom = '1' +d.parts.clear() +item_sub_1 = d.parts.add() +item_sub_1.name = '' +item_sub_1.manipulators.clear() +item_sub_2 = item_sub_1.manipulators.add() +item_sub_2.name = '' +item_sub_2.p0 = (-1.0, 0.0, 1.350000023841858) +item_sub_2.prop1_name = 'da' +item_sub_2.p2 = (-1.0, 1.2246468525851679e-16, 0.0) +item_sub_2.normal = (0.0, 0.0, 1.0) +item_sub_2.pts_mode = 'SIZE' +item_sub_2.p1 = (1.0, 0.0, 0.0) +item_sub_2.prop2_name = 'radius' +item_sub_2.type_key = 'ARC_ANGLE_RADIUS' +item_sub_1.right_shape = 'RECTANGLE' +item_sub_1.radius = 0.699999988079071 +item_sub_1.type = 'D_STAIR' +item_sub_1.length = 4.0 +item_sub_1.left_shape = 'RECTANGLE' +item_sub_1.da = 1.5707963705062866 +item_sub_1 = d.parts.add() +item_sub_1.name = '' +item_sub_1.manipulators.clear() +item_sub_2 = item_sub_1.manipulators.add() +item_sub_2.name = '' +item_sub_2.p0 = (-1.0, 0.0, 2.700000047683716) +item_sub_2.prop1_name = 'da' +item_sub_2.p2 = (1.0, -2.4492937051703357e-16, 0.0) +item_sub_2.normal = (0.0, 0.0, 1.0) +item_sub_2.pts_mode = 'RADIUS' +item_sub_2.p1 = (-1.0, 1.2246468525851679e-16, 0.0) +item_sub_2.prop2_name = 'radius' +item_sub_2.type_key = 'ARC_ANGLE_RADIUS' +item_sub_1.right_shape = 'RECTANGLE' +item_sub_1.radius = 0.699999988079071 +item_sub_1.type = 'D_STAIR' +item_sub_1.length = 2.0 +item_sub_1.left_shape = 'RECTANGLE' +item_sub_1.da = 1.5707963705062866 +d.subs_bottom = 'STEP' +d.user_defined_post = '' +d.panel_offset_x = 0.0 +d.idmat_side = '1' +d.right_string = False +d.idmat_raise = '1' +d.left_rail = False +d.parts_expand = True +d.panel_z = 0.6000000238418579 +d.bottom_z = 0.029999999329447746 +d.z_mode = 'STANDARD' +d.panel_x = 0.009999999776482582 +d.post_x = 0.03999999910593033 +d.presets = 'STAIR_O' +d.steps_expand = True +d.subs_x = 0.019999999552965164 +d.subs_spacing = 0.10000000149011612 +d.left_handrail = True +d.handrail_offset = 0.0 +d.right_rail = False +d.idmat_panel = '5' +d.post_offset_x = 0.019999999552965164 +d.idmat_step_front = '3' +d.rail_n = 1 +d.string_offset = 0.0 +d.subs_y = 0.019999999552965164 +d.handrail_alt = 1.0 +d.post_corners = False +d.rail_expand = False +d.rail_offset = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +d.rail_x = (0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.left_shape = 'CIRCLE' +d.nose_y = 0.019999999552965164 +d.nose_type = 'STRAIGHT' +d.handrail_extend = 0.10000000149011612 +d.idmat_string = '3' +d.post_y = 0.03999999910593033 +d.subs_alt = 0.0 +d.right_handrail = True +d.idmats_expand = False +d.right_shape = 'CIRCLE' +d.idmat_subs = '4' +d.handrail_radius = 0.019999999552965164 +d.right_panel = True +d.post_spacing = 1.0 +d.string_x = 0.019999999552965164 +d.height = 2.700000047683716 diff --git a/archipack/presets/archipack_stair/u_wood_over_concrete.png b/archipack/presets/archipack_stair/u_wood_over_concrete.png new file mode 100644 index 0000000000000000000000000000000000000000..aab27159f2a32a05d32a3f262ecff34a72cb682f GIT binary patch literal 18165 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 literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_stair/u_wood_over_concrete.py b/archipack/presets/archipack_stair/u_wood_over_concrete.py new file mode 100644 index 000000000..b523dcde0 --- /dev/null +++ b/archipack/presets/archipack_stair/u_wood_over_concrete.py @@ -0,0 +1,155 @@ +import bpy +d = bpy.context.active_object.data.archipack_stair[0] + +d.steps_type = 'CLOSED' +d.handrail_slice_right = True +d.total_angle = 6.2831854820251465 +d.user_defined_subs_enable = True +d.string_z = 0.30000001192092896 +d.nose_z = 0.029999999329447746 +d.user_defined_subs = '' +d.idmat_step_side = '3' +d.handrail_x = 0.03999999910593033 +d.right_post = True +d.left_post = True +d.width = 1.5 +d.subs_offset_x = 0.0 +d.rail_mat.clear() +item_sub_1 = d.rail_mat.add() +item_sub_1.name = '' +item_sub_1.index = '4' +d.step_depth = 0.30000001192092896 +d.rail_z = (0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.right_subs = False +d.left_panel = True +d.idmat_handrail = '3' +d.da = 3.1415927410125732 +d.post_alt = 0.0 +d.left_subs = False +d.n_parts = 3 +d.user_defined_post_enable = True +d.handrail_slice_left = True +d.handrail_profil = 'SQUARE' +d.handrail_expand = False +d.panel_alt = 0.25 +d.post_expand = False +d.subs_z = 1.0 +d.rail_alt = (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0) +d.panel_dist = 0.05000000074505806 +d.panel_expand = False +d.x_offset = 0.0 +d.subs_expand = False +d.idmat_post = '4' +d.left_string = False +d.string_alt = -0.03999999910593033 +d.handrail_y = 0.03999999910593033 +d.radius = 1.0 +d.string_expand = False +d.post_z = 1.0 +d.idmat_top = '3' +d.idmat_bottom = '1' +d.parts.clear() +item_sub_1 = d.parts.add() +item_sub_1.name = '' +item_sub_1.manipulators.clear() +item_sub_2 = item_sub_1.manipulators.add() +item_sub_2.name = '' +item_sub_2.p0 = (0.0, 0.0, 0.7875000238418579) +item_sub_2.prop1_name = 'length' +item_sub_2.p2 = (1.0, 0.0, 0.0) +item_sub_2.normal = (0.0, 0.0, 1.0) +item_sub_2.pts_mode = 'SIZE' +item_sub_2.p1 = (0.0, 2.0, 0.7875000238418579) +item_sub_2.prop2_name = 'radius' +item_sub_2.type_key = 'SIZE' +item_sub_1.right_shape = 'RECTANGLE' +item_sub_1.radius = 0.699999988079071 +item_sub_1.type = 'S_STAIR' +item_sub_1.length = 2.0 +item_sub_1.left_shape = 'RECTANGLE' +item_sub_1.da = 1.5707963705062866 +item_sub_1 = d.parts.add() +item_sub_1.name = '' +item_sub_1.manipulators.clear() +item_sub_2 = item_sub_1.manipulators.add() +item_sub_2.name = '' +item_sub_2.p0 = (-1.0, 2.0, 1.912500023841858) +item_sub_2.prop1_name = 'da' +item_sub_2.p2 = (-1.0, -1.1920928955078125e-07, 0.0) +item_sub_2.normal = (0.0, 0.0, 1.0) +item_sub_2.pts_mode = 'RADIUS' +item_sub_2.p1 = (1.0, 0.0, 0.0) +item_sub_2.prop2_name = 'radius' +item_sub_2.type_key = 'ARC_ANGLE_RADIUS' +item_sub_1.right_shape = 'RECTANGLE' +item_sub_1.radius = 0.699999988079071 +item_sub_1.type = 'D_STAIR' +item_sub_1.length = 2.0 +item_sub_1.left_shape = 'RECTANGLE' +item_sub_1.da = 1.5707963705062866 +item_sub_1 = d.parts.add() +item_sub_1.name = '' +item_sub_1.manipulators.clear() +item_sub_2 = item_sub_1.manipulators.add() +item_sub_2.name = '' +item_sub_2.p0 = (-2.0, 1.9999998807907104, 2.700000047683716) +item_sub_2.prop1_name = 'length' +item_sub_2.p2 = (1.0, 0.0, 0.0) +item_sub_2.normal = (0.0, 0.0, 1.0) +item_sub_2.pts_mode = 'SIZE' +item_sub_2.p1 = (-1.9999998807907104, -1.1920928955078125e-07, 2.700000047683716) +item_sub_2.prop2_name = '' +item_sub_2.type_key = 'SIZE' +item_sub_1.right_shape = 'RECTANGLE' +item_sub_1.radius = 0.699999988079071 +item_sub_1.type = 'S_STAIR' +item_sub_1.length = 2.0 +item_sub_1.left_shape = 'RECTANGLE' +item_sub_1.da = 1.5707963705062866 +d.subs_bottom = 'STEP' +d.user_defined_post = '' +d.panel_offset_x = 0.0 +d.idmat_side = '1' +d.right_string = False +d.idmat_raise = '1' +d.left_rail = False +d.parts_expand = False +d.panel_z = 0.6000000238418579 +d.bottom_z = 0.029999999329447746 +d.z_mode = 'STANDARD' +d.panel_x = 0.009999999776482582 +d.post_x = 0.03999999910593033 +d.presets = 'STAIR_U' +d.steps_expand = True +d.subs_x = 0.019999999552965164 +d.subs_spacing = 0.10000000149011612 +d.left_handrail = True +d.handrail_offset = 0.0 +d.right_rail = False +d.idmat_panel = '5' +d.post_offset_x = 0.019999999552965164 +d.idmat_step_front = '3' +d.rail_n = 1 +d.string_offset = 0.0 +d.subs_y = 0.019999999552965164 +d.handrail_alt = 1.0 +d.post_corners = False +d.rail_expand = False +d.rail_offset = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +d.rail_x = (0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806, 0.05000000074505806) +d.left_shape = 'RECTANGLE' +d.nose_y = 0.019999999552965164 +d.nose_type = 'STRAIGHT' +d.handrail_extend = 0.10000000149011612 +d.idmat_string = '3' +d.post_y = 0.03999999910593033 +d.subs_alt = 0.0 +d.right_handrail = True +d.idmats_expand = False +d.right_shape = 'RECTANGLE' +d.idmat_subs = '4' +d.handrail_radius = 0.019999999552965164 +d.right_panel = True +d.post_spacing = 1.0 +d.string_x = 0.019999999552965164 +d.height = 2.700000047683716 diff --git a/archipack/presets/archipack_window/120x110_flat_2.png b/archipack/presets/archipack_window/120x110_flat_2.png new file mode 100644 index 0000000000000000000000000000000000000000..25f21c0a5ba0eae99987b6e06c6a1d17308ad841 GIT binary patch literal 8410 zcmeAS@N?(olHy`uVBq!ia0y~yV3@|hz>vbh1|p+Reqdl=;4JWnEM{Qf76xHPhFNnY z7#JA%OI#yL+%j`g8Ei`PN-|4wQd8`vZoUrEECG^oNi0caFfuSS*EcZJH?UAJG_^7{ zu`)3`5`6YG0|SEqNKHs)ZYqO;ffW=PzB#OR2xK2f&aEgBENOT!P*e%zI*_1qVs2_t zA_IiV`2YST0|Ns$NFq2nH7}I`Og>eN1vx?(Bpj5Qmy%k9utv|oV%?i-#S9GcXFXjU zLn;{G&dppO?OxQzf4zEZ^tOi+CrX@1dh-0o{O8pT44w%r2M-ue$jL2NomsW(ytkP5 z-Sme=O9hu5-xC`9_1dMoOYO8RC&$<QOugJ<Hq)m`r98W6ORSZpbdx2E_H6x`d1mu{ zK0CgYd;L7%_r|i%FH3frA6R$PKE%C2@Ah66x1_~ypTD~<mT=~W<l&8Vd|U00tV=Q8 zv0<0mGVkLmUtb>lVJ7xISH4T%S8e0*XLUXEXXVDrE#Icy`}2M4)!@#pt{T2+(^n_| zk;$s=h&{V(b%eKBd`e<w<1xK?Z;oBMbmYYG8!}n@e*L-f=EUOD-voBIZ}_>0+q?U= z`A)g#Ki6@7*|Dzmv&F~Qy5BWdb9TSUGJ6udSvvc2>-wWFH&sqJv`V$@&z!YpFP)Rh ze52;ycg{I}tK^|%yykJe*&BcClbA0Oc6NdHz2(m9!=xHY_&s;4&9wRT>CL9~H~Y3) zs_rvcruSdfX468m#aye!cy>nR1})XU=$zwKH{mO9Oj)hr?>|%C9KZEv?ZcV7E#BRd zc*dZ-BmYx&#m%WgA9fXgaxPM?%XOL(ee!(7?DeweV}4eCdVKd?gxrFYy=`%N`_j~F zmn46@xNr4)_G_)q;ind#x4Sh*`2KV2zK7n2^SQmb1=amOSqSKVubIC0_}u3AUAAS9 zPOd+%c1*h2UUgbbtzPXud8Yac3nv`Os56K(dh>o^nX}rOX^uUXN2YI=Z2mtd;@J8< z?N18z{+uk=nXmCjbGDk!FBz-Gdw%=cZuK3k=ZrXJrPd$(*t%Rto@d`RlZILU?i9(_ zXDrlEzsa|i`M+wy$^SBy557;Z?EG1?DY^3ahpo#VnErh}Nqham$?I&6i}92>C-t06 z=b857`6<c!TMYjx+r5oA>HXWi`Loga?=Nk?6@N-rZ;j#K_;XIOjmq<l$u=VUb#-LE zY*n0LCm%n#?#z<;+`k1rTdI|RV!U77xAX|(|Gl*{jSEg3;<xOPH@>xg+lCoCHvX45 zcB|*8{N{lA>}^LUf4`(~OfTr<apx0X^e;Zwp82SHZpw4LNyWe9UsvnI6`Qo3W1OzG z>q&~nx|HfX$;qytEf2-V>G)dYhI!g&UVB}-`C)y)<fZ3l-Cg^4le4z;r1kw*Tjh68 zNd6eR(SM<}%KU@vPV*mJ;QVU#{DI_#s52+MmR9boy1%FDyZzZTtNd%1Q#a4lo&7cC z+M4I$-!>-AzOi5Xb7B6bUeWtfb8fCZqq|RdD}zg9w7uxk(gim(|D`$4|37JA(5*`| zZifXX9MgNW^JA+Bd#78h=ro(YnOi3Pm6$0OWOjGsTeoGRC*S-sZ~o0{e(#CNmjz}b zGdj&CyIzhcT)OY&_08OqqZNZDh%Ec4t$pg)z8hP@6s78}O0dnbTqvQrYisTA%v|e* zf4A#;DXDF}t;_hM>B@{Ykuw53k3*!pkG~6;otvn+gKgQrh^jTVA0>QN_I}+v@0Op- z(TLd*iYaQs|I~cBQtRLBKl$t$-^A_Fme(|dMD;R*)hid=n6&(*lIW9TNtevRro7NN z*ZWe#ChC?huXEn&vi6wh#Pv77UQ)Cc*KL1w`*&z@efjJ+kLGXWuH%?DBl%g(>$bD% zt)|x^Wj<*BN@lw(+RwUJN9}BFac%kiKlf+zpFMs2^p?XuYZ}DLEav#CpS_~iG{g9I z>VvhH{WO-Qv(9v_URbtue)syw`w@w^(z{O;`IfD^vzG7Jw^Q40m~E5Zbo%MtOQ$Nz zW|q{)?b~$t%cY+z)4Ho($n+kaENSNaw|Tki?4-oh7dx`1o_)UTcITF-9_`AvS<|+O zm9H-Se0{t8?C-PH&wf2!pD7j=z-^uQoZ;G<cW;XC>s()*wrP`W(&j8Z_3v$w=C7}w zx^n&OrLGC8bA7L^WVp#Vz4+UvUeU!fZ}1r>Z})QbsC?>wOQTqK$;y*C+5r>S<V||7 z_IBE~72ETJ`Fub78t>e5Lo6(2&%T{@EdA#VUANB7|L3^tT4cnbUQU(S?Te&lJbL^5 z)S=tQuN9^-|9|%~_4BnK(tN&~D~pqh4+<^sRW_W`Z1_!{{oG1XxoyGPCQ=*cnQlL8 zzostt-TU{KA7-bve&2uH|5EEgW$h!!xP<?l;hOw;wY#x)*d~McDQZ8@riXo5*8Kdi z-$}*lSz0EoUe;pmr%dET7bLcuob}u!n_wW;f7))gW9!m8ZE9M7ELX6p-2ZENlk3)| zjTvvEwyiqmpL=jq``e((V-?QJCKqn&uHGhFyxH(yvuvDrS%p`tQ(}bR-^x#$D*wE> z{cP^d02dL}f8pD@3k{Z8-IV<4^B^~U)7lM@_csUMGn>9`so%SGv-0(O;%~9GZ2hVh zzw}$b{=v)gXU_)A-dNGZvsJQG-rHGX){YPDLA-}<_jjHxTDMI6Y|ht<WvtPe+2Uto zKOfxI-T79k*7u|ErGE~^jzMd7o6Rl`o%wg;=O=%Ao#x#5H;ucf-7N2$MfqpeWp5YF zezyB$X>sNMc<UKA{oY=frL=Wl`Kj8x+3Ea#VeOAL%3J-vEt|dKpu}^j69KCjx$V`| zZ@Qe~EZQ|^JL~cLN0r`(eK3sM()+R4@+lLO_k5>Ww-dKM@&9Js9m{gm&o0M(&h3Sh z?&>|wa#|-fduD7!NSEukiZzL47j@2mE3gvb_cL?;qdhHWTdS$sgNQXv*H%oDG;Xkq z_dLH><(==-u8e!Jcg@Z}EBlcd9dGk=>(RpzQ<ps5-s;7AxM}ND;lHlcKRxrD4(hx) zWvY7X*(|=+vVgTedy3xQ{(LG?XWx{?-(OcA{a{wz`c(9u!6)a*J!MPnJY^rB=jZx= zPrQB6)Vp_54ldiKZL@bvv9fhQ;d7aoExkW0<6lG+-~MiNxoP6kTUtN5zpeFD`n_?b z&o}L(8+QMDnU<@k)BpC9=Ktr%gqQ0ddvQq0$bk1l%$EFs+dFi=eVf!T{HW}u&|UpQ zYkd>GB`wS@SC829F?`XbI~BiK^8dTMsmZk#<13t|wRP|EwiCxLUay%`{Giib?bwP# zQj-eqv&gKHH(n9TBc}4+XU*9=eh+ps9TrdiXjg9bdyaf@T>1XZ65Dj!X4&WOe_vU@ z@Xdm|t1d@Qla+XT=R(=dZ?lwV<^7g&4wq?cU1~38e=V&bk=N6O*}^nbvFGD#wToTq zwzp5;b|}u?F0HoxPPmlW#v`|*d01}QTq>K|?QrOlOl9?%+RqicH&6YxZ%d@)@z0)l z6E)^+|7^FHzhBUvt@~_5&Dpeo#ILINmwxZe+AuHacB|v{r}uw<s++{Sukyg1JniXa z{MiTRGR`~An`5`FY)y_=;KnyU^K)hH*|ml2yYqckewnzanPORJMt@Px@l(Ga?_ixC z|ETn6xZUvw*@oY^R!?#93@cvdVmo!O|Gn4#2TyNL%B{V%D?ja9!3`Fj3wPvB-?@9& z<a^onKf-UWy$DWwEg!mS>7L-FcUhjl{kZB$@V5C?cjHS=8Rs^!9jV^+Vne-zh~(NQ z)|$8M?fm4$CL31tA1&+nf9{Z$rN&8)Yu8i6Cm-R9_3ZO0Q~kebW%3(_$y}>z*CewR zq~`M!T;Hvo*R%C~eK^B|qKns#Z^+;MKJsVr`m#L!jobg7d3i<d`I9Q&r@QlSd`t1) zuFY4~FWde8bL^-6x@$6Ie%GFKsgdFMW>R1p>&ElDyI*2+MrOOP>9iLYFNvLz<!b-& z;pe8#C28H~&lQzc-_N-v{Zcjl-nOl4Ch?x{nG{}q>GCt#+N%0MaasAUqvs{=tNvMV zNQ+hT<evk3?(ttxfB5u4?fu_fHWu&gU%kvX%vB9w>Atz8^7p@Wk@n}xAGT`WtiD;d zCMNRMN}UO^M>g#b`uBU=P3;}GZ{PmnSfAPc;^eZuj<XcXE+#&2vw88%RPO)emk+il zXHWi8JKtGzoBZ{K5j9_a>WNJ=|MW+o-LCNCr-$rHcOzb@Z%kP&VYy6~sr^vN_rH3+ z$Ckd0;Fe?B?z?kk{Gv~tdhaz0c5b<Je_Mt3zCF5OCS1>6y;`+%=gIY_*DQPX^b%j| z+q@mytlzEw|6}pa-OuJ;+0$WMy~lWa`lK3-El(#+{vDIDC$Vvlnfalm58nA}#;;7i z7aNhIWG6b!eAP#ZYT4Q-&uOOf*@VM9FNo{M$JYMT4ZNv&Ozd!T%x+JsjjQ$YChV)L z&+YcGd{Z?2?KL%n-*&gJn)~RjnrVCK^!YDdb$fKHy1JT@j(fd7w>;uxaJ7Jnz8`b= z${%GHi}rmIf0>lX-+IP8<mV5iB|kJgQf^G@u4#F=EN?oWgG}}je!Vpr62EIt{&d+8 zdjGSkymQLj$tjah>arW`neqKZocGLgxjQ5FZu{Enx|7S_b>{9TO7Ze(z9;*)FP)&V zs$bsTOvj{m(xcYHFI)CKO<^iqWBt40c8hAp(!h`h@85ko$K&&}BkQ`d*$wx8xwnab z*0rBKdi3Zw1DWE9=Yy;gzV8cvanSk43z69~8VmxT|2B==w&(}j>u684+EYi@tuy=V zZT)YL@A`9h?ySv8Tt7?wnfG(Ot#h6pKYnL->dR|4`yI;yZVELo+O=<AnRTe!qc-91 zHb%RzCb%BWz4LcsTF%?`#~4KQ*Ay*Spkeb;)9t<qn?~{HKR-V|J2_cBljqaJkB^@E z*KW97tkYs7_I1;V>AzHG@^{7GXRq&DCLzVX{JPowmo78vw|bw;j1fN9xou5NF6*%q zv+j1~Ke};5we-v5T+J2y@^(uOPo18;W6Slql}ipwiq5MDHJBf(y50Tn%Tt-<8u$1e z12;x*T>MggzWb3s$vhiwANu6=aS=nd&8~H7)~e3?``>?hUixEV{Yh`5z12PM4(07# z|EA{j;|JMif3q*GRrwr|8f~xlb5hUemF4g6Exqex6}YBS+?#jZSB;C!K}E|}$l2HJ zu?_G3{P_71_2-52MW&ex>30dAKb`6MtoekW(FfIgzbD6SQWTjz!~53SH@h2NRCI^L z+dNI|jM_3$=GEsLQ2`w_TY|T;UF6>;Q)jWft6f$;Ing>yZ~311yZLWkC#=oymp*;G z=GU8p@o{m>4$r^8HmmN>od>rcuWEhj_{eo?!W-{*%|9MIxcYt1cIi`(k00;<J6FE@ zq`3cOyM%DpZD;Kx{oZnmZu`Ead-<F)GiyHyPmPd$hPm-4J(S`W=f63%*+lMK`n~=2 z^VhxGmGnU4FY|)1-sflAo`@Y~HvN>o{L!2){^dLyj~X7?`SC|cgq(t1P>)LYo^pxG zn;M$G9<@&O3Hg+)Svbj`>)^X>pZR{C{kv=D?;i*EJxq-+$x!F_S$Wjv;1Z?>TANKH zLeFiqsDCIBVHMfGQE!J*3cFA0(V5Yf^E1j%*@d@g>D*n{@mSv2zq(b`IZ#Mk|5a*T zLR`O%+S84XHoiQ}d+@>G<I~TpC?7ktkKt6((yg;i{NFCURPIsrsCxeGIY%Xyim7e; ztoo`(aQ<QW=b^jSJ~;F3<~FD3NhL9&b}7koWPa`73JDb4Z&LSUR(wSi=k>^ldk%Xt z$|B2FCD<{(x$=F_pE+GZho8@o+kf+JdD#5p`8G;B_wC!ny8X4@<K449^-6GgzAc)# ze~DN8<4@rYhpuX9eh5}Exid+y{ZM<(n=t?Rc6aZc?cey==E}3PkJ&`0m4`e|Hu};U z>hSB4a@;Nt<vu&RJ^3G1bD~yR+NVzG?wI_2r>*gse7-5VDsMl{?m8W_>coLpACCJS zO)SWGeEaIou$}Cw_iy>VpTN7-j?X=?_W$4S)#l!dk6*Hxa_!<{H_>V4Uut|(JmjlI zJv2`$+9|pHQ~Cb)$n8aI-#ky4_D$AKY~JK6QLCR$IW4SxuC?g0E{ET>o#CzTXVqqJ za?erwRCd+ty<EoeM;nemJRi^aPW72pq0QE>;_n=}yj@-9%`2~zKVrned*QRi?Qbhv zwj_UiwW2ohDqrl&?`wSC<Zk@vYo&jB@-}VV@W{;Vj|5Ka6txaNmiBs*{?DVg553!b z+hbBklI_*Afk!n0yI$SS-=90rwtCt;dAFYP^0Apbf{zQ;b#++vS$jI?Z}zWwI_c2| zg<E2Mhk5qTHkciEX|ub|x5=*CiqE}~R5H=mt=}sClqqzb<mB>IvWE=HQeXIAd;Fue zUVr_yjWv#)>#RR?RcriBk&AM_<6Hmnk*n4EM*$vwcJ2vBZDu-L_ma5s!)nC_FTTAz z`=(?}*(uYX^zrrM>h#QwH#T1KXT8Z^7Lm98+w5Tdxc}+J|9>yqdFzJr%hTyUeS@t} z&e#2Mb${lrz>0kJAKbfx{ZDR`h+zyW-qU?fW;r+W<wC=PRM%DP2kq*fJeK)jQ<Jz| zdP|DPRZITXGs5k5e#{lZPM_6YeeT`KcQAqfolfi1$=1`tFS<m1)|UBo<nRrL%lr#& z+UnE^zn&<1{xP?8%FWo1ouX&b{zg|t&y3CeT6pQsnm14WT|E7)|9kj1V{ZLf-^>5Z zQQ_bGR=qXn`|ry-_hpWnUN6qC`+u0r{JzqKebcoB4orAf#xzfAnY^N8QaMB0qF>Ku z=XX61Ju^MunQLX6WptvTf!eFdcGJGIs>H`_JAV7;k@x!l*6w`Tx<CKTS>NA#S4;nH zdU2+1dKS~(>gzslpYETz_vp?I^8T}LpBFqER=+Xd@3X~?yk(!54a}b3N!|Xr`1YSm z?~ke6Y!!XA`t|*{$6lW)v)8t({{H&dHls4TIy0kM&uh<={+`!x+xg>@-f4f;rSrYL znhUs3E2-~sE0?%x<6!ybBhLi?mk*29del5wIAgQuH2-DC<}dnTQW$G!7q@=a|9`J< zo6O{{{#=x`zkh3lo$u>oDYxfMU3b{x{Uhm<*}nz1{rnqRTwgr5Zf;ub=Zo)5152)l zv3&pb_lWiHx!Z1++&=U!#>C$K<a?*Jm0yo`J<Q9Um)0-EoxNWr<9))~<!4RX?{ptO zJG(z@$LZ70?rz;%n7Z-#p3H;q^SAOZ`drN?vY>OVgco<DbhhuhMM=vaX-zPkF16!E z(jP^Mnk9#t4mQsD68Lc8zLR`6CwRU*RPwUM=cAnb&-nVkTYtXYem^Kt(>-wRo}zsR znIrzG>c5?N^Gsb<#;+fho1b)dT(dCuHaB|Pm7ldgbL-BnN}H8@?ypO{zI*H3vs@`V z_ZjKzum8HSGVlBHdAc_z7A^Eqt2bG_d|O4>4Rd2{H|zJiw+q+T$z2aqIGS1Ss{L{K zufq{Le?4xQd)!RQ`ty_7PvXir*naOl<kV2ix+3u5f~{)NFaIWamuXDZ4}5?8^n=Vh zq5pn9pMSZ#%tqvyA@}!Lf4cMUXgNHWP7ZkZvCq<G`b!bH-S<8{%B;J`vCekm{Eyjn zw`1S$y8HEw>66?2FSi}qvoU<J<&#@yzxImWZ@sxTr~PsG-;8fxJHJelxB2+DVuI|Y z6LaUD5*Mv|B&B$H&cVxkM@6_#TTglHVaxU}WNo%m_O3}WMxXw@Ts}Xj>cozs$9BTz z#$OjMc%Z3gCo)IF?7_>`ijNP!Th_mB!=*<XU*EL6mh^MaOr1EB?Qho|&$fQ~v2^$A zwYOvY^~;xhw*K9?W2$W3^RnN~?tVuswgrZ)kK5}N-njJhDhcP;Yjy<o*Knx4{mj5B z=jF{f<;+UW=HR&4ec2Yie0CZPzeE~t@!S8Ika9v($!?FEkKn63tE1n4eSN*;FyER# zT>kU(@2|*I-1=H@_Kp*mAC`U8x$AtHf4gbfy>HY1|M;Hvv){Wq&)oC-F_G*OKSaZ) z?6_b1{qDVNgHw?+UTpVYUz5V$Yd0a({~l|fiPa>z%C!^M6g<kl@>^h;%4EHu4U?u; zq?~fC(%`jhx3UvylsbQ@B4qJg9!tY73pYG)Rc*hd;U{&}t5GSOugi1e!urp@V?VF2 z-;^(Mb6urRaA4t~a}`%a!fT(dsJo*4{;-R!+0{c%D-|YI$=dkNxO4mO*U393mldnd zp0wu4(Z^XfWiK-9@=s1W^q28)r>**a5l+@8Z;zao(~?`m<tH+?=D_Q>L0u1**Z+DR z60#{=>WirRCaI+pZmg_GdHlbzp}%hN`4z{P@dsZLKfjTs=L-8ozR2*;JNBK_e7QU2 zQs*z5J!d{Ac-20!3xDXc_4S4yB1{o~J_xEAeq*0&SGRqFoHSSWvZUrm(tB#S^t)`H zY0TBA(hKu)WmUU%Jfim3Lc6Zkr@f*YK?Wu#i`W0V?%(ypWAE0I+D()0-FB1PQ{?vi z_lHT9mKE<JiUmJB?zhj2f3Wy~#qvi2oJYf0mE%9LhsLbn@)P-^DRuPZL;h72_tt-! zsFQT;)yLpZ8*MAUGcf+n(6v0d&?{u$AC~GlKPK(-R-3!xkHg+Gp97cMg;XB8ykW6o z`o<pxOI@ASZKo`{bemU1cDsp&g~t3zymgvqB+MJR>pOxEDLvot{nOLaCFi|ZSO0mU zb!%Rfzf97bw-H=Xi;Y&PYVKVAsr3xV>9&5mX7N<!Y!Y;puS_|`J@@@#Z?UJBUS{>z z<ybAB$iL!$LDRX!sfRBAyfxcAKd6w?J@4YZ(@8$Ro`|exn3z51gUQdcdq2PY&efgZ zwZzR<?bDa+UdEHle@K0`YtcNpWOZQ9lJ1(d{V|n?E`L~fZ7H*G_qp4PmVZzYerhMY zC$DhB=anCBe7wPWvvI=90PW~ciN_D?x3Ye<<Z#c4ivPF#l6;AVj;e$((_?m3TceK& zNt5G5_EucXcx)K={NqIS<u(_7+U-dRv~vsJq<gaDhL)&oWq{9<SNvN=6s@{my}iBt zrHnw2{;D7y`*kb&cO`#WT&|<b=9sy0{?ec8uWM$Wd~w+Q)!U`oCzl9|y440giM6SE zs**FM@~7c0#cg)tM?)W84F71TW?E(=wPVuWf`Ikk?H2X@GyHY$&?m;)pn2aVD(+92 z^I;!<{~ouG!lkN_M^?Pw7`vw=;OWcM*iS27Z|dG<cYD%4mCvomZ`7{tUH)YGX|bB= zcCXlExoU;%0?#*Vn)WO1`1R<whMn@U<C3cmx7~Pf)H%Dh|Kkpg{#`4k*FQ1owf|oB z@v8mOe=}}eG<zoZ)$sj2+0)Wjen^B&X8Fo_LQe8efR??gwESxR<rRk<YyI}UH-y`~ zvC97X{L9mBR9lNRMO|j<;1d5msk>?~Tc1z2j@F)E6BqSwT3DMe{WD6wQuDjUblZ@% zY3=u3zc6OIzsK&?fzp?|OZZd0Wj^_}9-YcMeUlVNxLA!x$ev#kt9~{rbl3j;TygTi zq0`Hbc&lmI2b}!1;`p?R6g&3&;``K`{U+Pm{QU8(wD#BfgVPL+o>_kPOxWU}v183u z)pZRCKUQ2g(%sa}J9n0vuhG%ZMJG21#xHJ9*r2$tU(^25&${_Mw!Z%!nA%m$x_<Hd zFTK0ZH}zWfp1gT`nJjmsyurC7=EeN3%hh%V9b7nB?iKTD{Vw~D0kb}>x~S*l`8@T} zLc4kIryInv`A@dpBmY?9?(<D=Pko;;$IkNjB^B3K>Ym(hnX{)mFKg@n5y<$9u`zJ} z5$~`?U7{<CL|pG5U3}c$cjtlflNT;~{m@vhAOFE5-~ImU-6h9wl>O}8WoYSo+kYL` zUSr;nE3Y49#!Xk<y`QC<vHVt!_rjGwe;ZDEC@+6J=+8;_PWNRoe`oAUxp4t%%$8ZP z^Vj*;ueh(bd!r%$t&JQ`pF2}LzsK8ee6+J*!AgGp{aRKlQeIr0kZ9hLq@2^~zD#0i zjF-{R##JZTYxAA|i|i`?DLeV~gpD`)Z#}&p_B5hd{3z$y@3!-|{;oRtB)ImC%I7^P z^9}`<Ogh=FZ&b9L-|e55NOb=~{W%pYk`@NPp46zBm|D$u_j$)n)4nLX;<%klGaqgB zE;!e+X3Js~=HHK*y`HVS{+qSS#^UJobH69@Na@RL=>4{_UtfRm`ie&j6KeMRhaGm? z`MtR3oka9mMY{|s?LR9^?&W{{@6fP7%RsK`*RrEuO<vW;?-83;*EwA$=;2fLRoj&p zW?ek@{osW4DPNTJdrJ1jPv)KIZhtRr&AHphl0NL(ST~b<>x=IJv0r7h)m|~b_^>i% z!7eS4ubf@|-I7l)b^nqHae3kO@uTW)kEiYuzb1;iS>1m>`RaxfnNJokGhAKIC%f;d z`7!BL%;LFEi#zhFLtilSJ+<vyP<YU@hrd0T%db83O-(oR1V6iv(Vuwzwk15w7FkoG zDmU}{)8B`#r#L^o=fCCWr4l>&v)o3e&nAUk&h1xaVv;*l{_w)ZC+_W`s*V#6W|u!M z3|bo)VD;ssTvxvT-r6gg{0|Sh$;~`I^_S^Q0ZWObU&|`u7r$HJtSJ0Y{iuIfr?^>= zuSvXLL-@jj-p{+as-M(`{H$Cvz4Rlf__nOP8BjU>?DZAWwHiH>9p*1vbaL<C{dJBl zR~K=2?#+|s&_APSa%`f%=+Z9%KgH!szhp`+u1U?@FB5b2nanPUZi%gT&M&oo74Twm z)B3HK7Cd6gHfquRIq7Ub7k~d!f0?5n3~lvgLT=ba@^;S&`!w0qdT!B#JGXl;@^-eq zfAM)r*~`kVa*LMh2W-PO=}CRu7yn3ASoi0&buNo}s!w{=T=}{0gL8_U^v5{%_tFuy zdt~B1>D52KaMY$oFV^a}?}oceKM2H~vkBXzXmac4zUNQ9H%W=|Ng37}y-Zzh7s5OD zy7K!;t~tE#pT?TCzxp5_t(l*gWp(oSvyHV(Z+Nr!_&fbkyz=|F-M8N<humMiUGjR; zRD(m+cO@cuUq8LL{rbV<Z2MA}*Dag8C#8Ii-m~yi$?u=UGDu#VH0j=Dp|D>SI_`Y? z{PlWP{Hd&d>EgXhWAXYM7xiA%$jki^n0ef7&vi}y6qBFr_FMISeg1gtMcb1dcQ@8d zIB|J-WqahLRMnkdHfbmBS##`k|CdFRPVZl?8hfSMeqO)DSMM3KbWUa4?WyVM-QL{K z8z1|R@9I&DFWDQnwLWdG+;`gIkyqp1HT?0H%1^evvRT*NZ*_nDS>_oXs}pxWiM2ZV zqU-X~!{s@jB%glCwmP{x$@6+;=n4hH9ZW7iC+uV5mj11}(_m4;5%;J|m64s(`&R_t ze|`K!R)l{4zV=rKj<&6D|KNTnO6TRO@Kz>=T5Z*LU!8xvIQ%_(&!4K=DV1|80ymiM zTk`qFisk;y!G~OD9xr=2>GFr=B9DuIN>Bd%pLyybi@EQ0&Vd#yFnGH9xvX<aXaWEh CsUZCT literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_window/120x110_flat_2.py b/archipack/presets/archipack_window/120x110_flat_2.py new file mode 100644 index 000000000..7c7dcf9b7 --- /dev/null +++ b/archipack/presets/archipack_window/120x110_flat_2.py @@ -0,0 +1,50 @@ +import bpy +d = bpy.context.active_object.data.archipack_window[0] + +d.frame_y = 0.05999999865889549 +d.flip = False +d.blind_z = 0.029999999329447746 +d.blind_open = 80.0 +d.hole_margin = 0.10000000149011612 +d.out_frame_y = 0.019999999552965164 +d.blind_y = 0.0020000000949949026 +d.in_tablet_x = 0.03999999910593033 +d.in_tablet_enable = True +d.n_rows = 1 +d.radius = 2.5 +d.rows.clear() +item_sub_1 = d.rows.add() +item_sub_1.name = '' +item_sub_1.width = (50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0) +item_sub_1.fixed = (False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False) +item_sub_1.auto_update = True +item_sub_1.n_cols = 2 +item_sub_1.cols = 2 +item_sub_1.height = 1.0 +d.out_tablet_x = 0.03999999910593033 +d.out_frame = False +d.y = 0.20000000298023224 +d.in_tablet_z = 0.029999999329447746 +d.handle_altitude = 1.399999976158142 +d.out_frame_y2 = 0.019999999552965164 +d.out_tablet_y = 0.03999999910593033 +d.in_tablet_y = 0.03999999910593033 +d.out_frame_x = 0.10000000149011612 +d.offset = 0.10000000149011612 +d.window_shape = 'RECTANGLE' +d.frame_x = 0.05999999865889549 +d.x = 1.2000000476837158 +d.z = 1.100000023841858 +d.hole_inside_mat = 1 +d.curve_steps = 16 +d.handle_enable = True +d.hole_outside_mat = 0 +d.out_tablet_z = 0.029999999329447746 +d.window_type = 'FLAT' +d.angle_y = 0.0 +d.elipsis_b = 0.5 +d.out_tablet_enable = True +d.out_frame_offset = 0.0 +d.warning = False +d.altitude = 1.0 +d.blind_enable = False diff --git a/archipack/presets/archipack_window/120x110_flat_2_elliptic.png b/archipack/presets/archipack_window/120x110_flat_2_elliptic.png new file mode 100644 index 0000000000000000000000000000000000000000..6809b6fbdfc91fba2e51e133457d6fcc0f010bc9 GIT binary patch literal 8593 zcmeAS@N?(olHy`uVBq!ia0y~yV3@|hz>vbh1|p+Reqdl=;4JWnEM{Qf76xHPhFNnY z7#J8tOI#yL+%j`g8C<Ml3W`#TQ%j2Vl5$e>QVvMQ&Szj?kN_!gNi0caFfuSS*EcZJ zH?UAJG_x|Wure@C?!IQqz`!5?QWKJyo62BdU<E~nZw{*+0@(_Zb1O;&OBx;w6jcJb z5hUoGn44OZ$N-@-{=a|8z`(!_k_b*t%}ZqflTQ_6LH-a12?wR-rKA=itkE;DSoh{y zF$04FgQtsQNCo5DIhn;O=0$B6#rtP2bel6V<z|tE2;cFxx9k7O|9=1XU}E**sN?Il zOmmyn?YznVdGn*&XZ)B-O>WMam~?Q(IhT73lNh){?=HUo|KIo7BBwG;xTY+NT6{S7 z@yQ7JnYAfPKWiR7`^?Y$z398zw{KS4hvZwy^_NTCF#GZQQ^AF0yM+%p++6W{<Lj-A z5|`?36~tTre)WEF_@$R~BXVUgA6q<m*N+oFj6&|cwiAnc<x|7o``4~4YgcTc&6c>8 zXX|~}^!cpeby{hB+vo1_bNhDvesQ(r)}*aVK6|Q7W6OB)z%yS%r?e-Br*~o6>eGI{ z{T8=MzF(~7nd@)0(PT&L+}BTS?p<q)TbKJ_&zuityJNRy?aHk(m3_4GCGXYD$?Jb+ zUap*SWECsh-w8{vy{xU-RvI<^Zf!~Xt1Txl?+fb5wQ_#CyXKDPnpsZLd%M}=mtJA` zC?~wF)TprJ=*?yEmt|Ldp73d@+18u-C!JrfHrCjfb#B4xD^{U@gzmU6PhpP=KfLt% z)6J*-JnL7STkkt>U-#Q}TNAqyMBcsnGi%QmFUE>{iXZJZKHPszv1@Hg`igV;vGewQ z`upVe?ztQHG+3EyRqgsPYu*n*zN!7ca!c=TFte;!YO(9f;&X~mcjp{8tI7Iw`^Y7S zBj<!|&5m5Qy|?WBQ@+OSKjm(HJaT=h@L%4E^&XK)UpJQAtmFB)cbXH^;vdIMrqs>< z{p#z>4N)u?)8(V%(mVbfpR)9C*KU8A_DS_0Bu}$*{hQz_E0+81t;2ft=-Sfu6ZKr1 zwv>9v@ax{UO0G<QuyqmJl^^ZPs{hvtyBd0KUoZD*ev{5ec_qF#?=Gtw&a*b2X3i6* zT@?4YUT4>%SGQkw6=xh;aQ*o42^DQ89{mq6OrC9Yq;AT2<FIW-pBEmhoOOKnkGOBi zkDRSrBfek!IV1Ou`m=?(cf#vtM@YQTs!iFI^Jm%*XRE&foBA)#cTQ55dt&`Pf=lM% z{i?*w-=^D&=QqCJ+;cyDPyOc3tJ73`m*2AI`mg<se{qUYPWR=NCsM5?<DGYGUO!3i zQ@!qX+kiWJ?r+N981Kz)=6TuYc$eM&+&oSbIsZ3OOCxVBS={M+{q@p^uT4Bpo5bn* zWctjzJ0&nGy4H5yvz92SnBMfvol)u^p5A&;G&O8>=&dzxAI<$XO;TU|#o9v4N&$;2 z-vWNtHf@Q!E!$$IJin;uX(Mm$&4?r)or!*KufLR(&h&dbOXpdpS^ZqM*{M=%*YT{I zrIw%Lwn_5!xo|lTwlpoFoD@yZrnBdC9`?PNwe;tk%w2Nxm;B6_weGTouDG}Nj-@XX z-f!c1x%;@pkJ~49ZLEFta<53$cW0}UuWe1%r5sw<pP78pV5^8_eEgF1&)Z+V_!TOp zfB*SGwVNIb*4C_Zo^_-B?Z!9jHJ9Aro!6*(y=tCYnXkBbq}Wek1MgK|zhusuUpG~Z zP2b<|^3@kAvA27+ZBx13qLTe)x_vv}qlHgT2bKl1-xZy9CYOE1k(z{A{whAxYbHyj zukQX*^fB3L=dXKN(-fxJDDR%(EM|7&jG9j7bna;za!!0m*&IFDOFR0RU%Kz7T%P&9 z>dWR|`q;xcPqlQ;`V?akR{6(Acb6E|CwFf1Jg=snSy?hq&wsy<xcd3aPphV+8%{g3 zt>}e{WY+wM?^kcCO_Pd|()sx7!0%U&mDMKvDZ8n*GA{1_jC1<?H~+mnwRiX1c{&sS zeR`(UyIlR{-=azXYW}L&T5i3WQj+o}Gb&<I<gKNxz7dt&F3TkAl1^?mn-U{6`^=GP zXBPjoI{Uo)OUfP}{eH1!=_e~IC!Xtbe|ch3zSZ1WyxxbtDxKA<_qcre{pHIq(_g;& z^7_laA2zW*Hx^I4k@7iD(yF0f(Z40O_FeSCLg(jxNxY`dc<n{(mKc8W46@xE`S{PL z`b$Tqt)9yJJ3el&@A|Owm&;$SHrf0%%WU?IxQtX!)4YGepCz()8fQFSyR;zh_L7(x zDP|{bJo0jiaOw-2{YCw>%Zk}2t!$0oloZaji;i8M{`j@Y?HhG{x-;J%k7$~|=P$qa zmC0Yu2)%hed42B8IVS%u?)TiZFV}9%p4KZzru{#?;;Z(Nr?&%qbY^~PT2#Ah!jY=E zYTF$Bx2^p5@of8-yXUXJeEYN8Wb@3<r>8$D`l`KIn)hGWJNL}oC(>1yPew<s)}MK9 z%>?oNXUtzdKdbZi<U2$4#hbrenRc<*`+@rB@|WG6x)a0Xqc_cutC_xUU)9t&+rMUe zx6bT2qhk4cnnguy)$bF@#kbGtu^&6muKH?=TG%xWjn03X4yI?K+?JTVyph`bw_vYF zyj}gYxZ7_M(zTkMzRumUHGZQ)q~oribKhRSr<Wr?ZQgSEob_L~Pu_XgV~^MJ$xBuL z?f&xVZvC>SC%0Et&bcKtYxCwL`Ata^`o%7uvD#8Ie@2z+G^tD56DQfTge*V#%}gzA z;>kj_=aC28ew;|_T5gudeVcn@W&BN@%YW)sZ`)67^0*gZqJ46MV^02C>#ClE8;!a& zbyu#>jsI3<=p8?&X7WGPzw^&#hHndrU%FoJf5Plv^<FRExL#y$d$L@_`O8m<PtG+3 zvx9Rb_5{7WePPl&fmv49UWe4)PRPsa<C*ZB;q&UtfxohTiSHJUxs~`_N53^(d-h%( zMtyOa61hT?SyH!jVsFoV)b+Q2>CvX8=ZsQvC+t2c!);kHxm%@w)rr7a=X>((Mc((z zPkVXurua+w%l~KHv+y;){_^Le>0iv=s!hw^T(bR`R!0e+ZvXFncH4}@;#(DuY5xsS zUEQo=prY0`mDzh*P1wGfb7j}$UbdCYy>!Ivt@Q5IkFWeavtViO+|sB+IdAeVrd*%a zBI8-GC1K~(_iEy&x2i6lyJ2>3w)e5tyJv55hRw=X`+2Nk^Xi|+(hfemb8phMX$fhr zpZht5jMV3;${MbHd#-4sS$gt|0v)$U26oeL-P?Y4(s4iexeGttnCd70`$X`P&AvJv z&OAI5kCsIT8hqcJ*_pciGf$qMp7feMckcQq`#4zs{FHI~$0Qb&DXQ%3d_AW9H9p$v z51w?SnAq^|QeAv7Wo4_vX&IZNy|OiWJI_2hm2+<GrVT1FQATmnZ*P5g-j<UicJ^pW z<&2cJ;;s3&pMJjZiN*9v`}LciI_DYIPu<6t{cE0|``zRBa@}uFh~6~8hUedrd93?> z7fgNj+S97CD6M^dnZ&7pzMS;j8^`AHnrWua&Y68RVxGq2*^TEsGfIVu5-(r4@+S8D zY!+$7{4brp<!9d<j$MAW*JAc9oy@6IeQVE5n3lC;vzgxK9Ji>T3(0M&2X%do*M3`Q zcIf@(@b+oyeR}fE!mIrc7u1{x%A1m=b8hw<%LRYW=H`EWFaEr2-4`8xGv>3)CN*xc z@jQNcqptfmhgqy=r=7U@%SzUNMf9i0+FidAnDb9BRy}$0%sgwkOz*tYDR#HBE@_=_ zt7KELo&989w9%}Ud#=yEDd;|VgD;byy<vcIf2!%XnYV5(Kd;)|>wF@)yrbW-^Qw9R zlZDI6t1E1p3;a^If1T#C`l^xa{2N6n$~zLjygk)DlQ%~$WOp)iklD5x-(&s<KYenK z{{Qof*4~KP`RVgk|Frr0@s~~RynR0IX1{%A{MGq+VESp-Uo~mQ56--^Gw0r%+TXIw zO!=m=%;{Kft!ZA*+^1bgJ9g;fsXV0}(O-?j)|Wl&n9YCkyQK8VMV29TEBxLcc_r*| z`AYl$CC<NoJYQ!JKh5DyuihJ-?BXL~RofF@r&VmfAajRfqq(x6@A-^M10Bh*Z(V2k zPd_%P&*z--a+={Zk!0&m_h%=j#k>gi&Ja7XQg`};B|ke=<-g6#zqjo9<8rs1o~o12 zzcs3z8uwO4p?-eKD`D=!=`~-x*66Y4*1Qjy`&@BXuF;2EZ>pnhq+@RD7{u+}zW1EP zT)CT`J9LX}rhYWL_2;CV^WF`umk*u(y<FLPuYK&TY{O|HOXpvIqB3vht6N`+X5Hc0 z7nGLFzv%zh&BoO-U#`6~^USeO5tEyG<-yZO$=`A{J^3fP{<r(|V)~<-o@*vKbcPwT z*^0gV{b2E#+Sk#yRaVL}NBBQoT5#%K+wScT91JTzU29V|EALk6QDul;9`njmVYkPL z<x9-Z#C@Bgu7Bv5%6p4*4(#_Y9c_JOr*{5)VoSf`rca8(&N+)OtM>b3Cr#y<k|FU* z>(Xag1=$K!E0dR=*PLJGq@FofbxFtf!VK56<abQFPhUIM`=Vpwch}I=uIHj3GW<81 zEt1$Ap&Nbk_u4mV&FM2vc=(++nb_WD`*FcaJq@W1A$#=xNW@?H_$hYNi>k_3w~ut4 z+Spgn$u?K0@?xNJ>21YT@3}YDA6K87-<f*$OnKuTd(*AF+qPEmo7)<%IK1gsBXdyP zenlbI;>yyQdU1cJJiD&5pe(^^v+`xv&;19AX7lIu`dL@1wwniUWUs5KnG@fg7k72z z^|HRcPpz^~c0P$VNmyGEnUZwhZLi+XxX#d`phcC>=f!g$x;*iFX}<QO6P3TW*K9th zedI&$3C&+U#oC)~cidS~aAKE%bJ?v6N7BFS+bE=d@<8>+?0bi;{vFUw;%ja0^tKlN zy}@ACNl&q~m9<`4KOPkC_WoLQ#AOqs=ceWU^WVK;_5Rzs&#O~k?!xJy_mlM1riFj1 zkZ(S5L`m9khsO2g@2o%f?J0eAEAGYO-8-CjZ~A^-yv*QKfEHWHuVV+b&rMsEvH02< z1@mg%eEzsCC(k%LO=Q`5N5x;xtM@tY*)91lv+8GLNT+RCfAh>`ef_sn6}1n1fB%;4 zSeiqciO}0GUrf}cwk-|*G@18CdrC<DWLLFQ&yRfaGL=a^zt~UnqSU$A^hu!tS6WYp zMaPzT3+$c4b|ymPH0v~{caQ7xG;aGJRc!jMDK+7F)6G>kABCoQO-_=$`*VVC&cuK- z8d85`9ZxG>>X2qW_iuKTvZA#0CuXfIc7gjUre=|0o$V~upPrmN=kx5Ww|rE%PTV$? z`_>y5Esprk$o1XGPT<9=>X<5L#ciAPrAxm>@W^m(*mC*(#Jvw+KGwLY_i^^wYU5<z zoXKzFcWT~jaFbEFy7leMq@bI=iFQ7FXU;C_;f>o|R&VvOK#<#5!klHx)Knpk^V5Uu zJM2Tso>#ZtE0B-zWA#3Gts-t)n|JtxV?TE+cz<Ed=1V1MA$_WE53a9`OgfeMd|#`J zX{_G2sMu)|4!VuMciFVe`!)T>y*-tar+d6Tp7pOyZPNDU{cq*C+t_?BTvm!(H9f=5 z!Bk*p^VC0fQ{QepbI`NSd7)?f!+kS9uep;VZ2B$tn&(Uz`wVsO^JV^`ta-0|gd!3& zp9r2`*e&lp&1IVNMV8Kk{lcZ^7r*nCznPm<>3oRQFzWE$!$Nwttj>G6^>1ok+@SpX zQGVL-Oyk<5^Aq&n_Oetj)Y~6>;NOZ!wh5E#Uy3{|I}_<QtLD){)7y`&?R*<<Hh6Di z-M-D!W0DDT^gN!*o+Gz@nYSzr2+wg|Q@!`bv{`GW@17hH=ea}SoQ>Xd)mi0AwihSp zKfE{Z$D*%|k;mm;7yLRW@oAlP{8MH32bzA4H`n|;rT%`|S*41c_sh<UrHLM1x4_#e zb93D0qdcc(3vExGy>t5#_G4H4k}l;O{pXb)Z(6>Qca!kb+fP<?KJDccpVAy5E~_!G zoX@wkzk2UJi?S!ovRAj8Jbqrj;?3FJy?bj7Hl^2Y-E?)Q;E8QK@i!LB#PuhxFU#>f z_1buQf0<Nb%4f@|Z`U8lZar(=J)i5ny9vAEA{o9*c0ygRpFDYTE;g~p`dhowYwh+L zKb?G!iWZJ;vBFndm1J36V|SFe2#eI}pXf2Q^__im_8#?JTl8m~d$ZP8&gHwVugqDW z<KDG3l2`Aw?_BI>y@UCS_XF954hi3G+v#yl7W?+$?*!J_X9840->z8rY_o~q(i@9p zjI29V*_${x#J`vnh@Ni`D^iPeEZOzs*NKGW<Mt<0`~7bu$3`Z7j1$Q;%s3&i?X=Hx zC5}Kt&6STXIxQ~zT{(MF+ue|g)SHX8?T>!9_SoUXJAZBK|JkUEolcDIOx}2Tx!=bA znCzs=!-rU(s$a9c%ib{OmE80{&Ifx-?;WetPyGC@|0i#|vUTmVw`Xe(WOlaYtIp_; z-FWqJg0R1k$%Xf;Us;*U1V5fApwAw(;gf`P`K{xfyVJDp-;+81wx0Xy-THGrnRZF{ zU95$^7VQ-}ExqcY(Yu8yPbcgu&}Y|+YYFVLGTW2;uy@VU-o4&=>v?CKIWq6_(l1ME zPrm9s8|7Us*}uNL&^M=Y#%*Q!lG*yPCY<iJw>MvW{`TCm30&JHq`zKLT#@q5>GJly zv(_|R6*|5B?Yg=DMADXT-n{wCqodt>M5dXP|6V&$qxZaBQM1r#>kk#0FZ85-y*d(< zyz7g1%}wvJCmXheH?EvN_uQp$_xwxdv*SM<e$sh1>biIJnT?iP^L1{2Y*2mJ`j?M6 zzU=926PqLZw!K&Qbne&D6Lri=>eF*C<ZgQSr8Y1AP{f(_pW9*$-&_{Yx#wwecHXA1 z0uw5>MAr9MJIHT7_9;-v+tY<X?mOe#=6vCkQ?~Euy>h9J=k0Rw|I^~<T2@xhy%%BR zGd(;r)&7p*X8o>p?%$fT%l3UOIkmlP-6ijPq5khbDI~Ywy7Q(d?Aw})`+U!qvsG?? z{pHv4FOOcNJ`%T&tKXnrdwFq5>i#t6rh)_K%Q>Z$-FBRmV5phq<hQQkyRG;e^Bc|e zpFaFNt-t?_ap;}!%m+`meDlBPu6HUS_?w)`#*{5j0(dKVemvhUe`)ph>1ENjC)Lj; z|K7Xm<;mxnN%yxuUma&`wlU}3Tidd_%~@&tg*I<f?wtAe(EYT^-1x@r-xt3r7278< zvEJtF6AQf`yVcc<H{QRqNiwlA?WNJS@&`W)O@FJ-Jbm@VzS!LI&Tm|w+PCJOS$_QG zThWu|o5PdeoZj^9D4*HuJEB29K50j2?tEeYHC0UR!@<Hso72x11-(plw3jp89miI# zy3@|+h0^x$q^WbNq$)H!Jd``yHEtYLzrH$r{iXW9-{rS?i~isEsJZxKM6+sBc3H~% z6Ss>jtT%n1{zld@<BX>Aw~R7hot4*WZXd7HO*7oTNj_}z?lV8xUt8RsfAi(Oom<v- z$KJ}HwX7s>|LmiGuGswASiC-Y()oS6^3yYS*5^FS_L>~D>aM_h-j2D}4np3Ztry=| zNAxFu3^4jsKH&&g?vC(KPr<~q8r!e$|9$s;QBrJ7%oFwB@^&>I;o;w$bHXa7xzAN* zvI>@6rP|EcxU78Uit`ow(l%ed_%gC8f6qxt<M~f{PNsJrIdh!HA~I&#RMq>1es<MM zQaa6|Ce@VfNVtCLTkhHE5i_^{$XoO>VMV}|drYf6l+uK{PTTWO`LIyP^RK$SLyfG? z#sUfH$8+zCuP?PJcZw02t3T1_&dG&`y$j|{VP07H(W~>G&j+iGytDp(T9alh@40+( zPTIcCzt65dd17<6*YDwJ8Rh7#*-xTxyh-2j_Pg5qeg4zMzf~_QD~Qf^H@on;+2UvW zuRov9cYC(ZjAE(~=hrOWaO4x?^$!*WraR4c$x9T7`lo+;xafnt??jymN0=w2cpZ|j z`=O{V<H_MKZs`2jX&y`Ka!1CjlSkD{Rqs#a{dc79&$p}5(UWrh%l~bkzkK^QDehlO zAHALS<HCmXvJZa#th8GnB%}KCh59MCeQ&CxYf~ni?^I<jy27f;|HZZH!mkpq**?<e ze~Y|(s;noeU=!aF>3LkpDe;<8p@-4ggcqLTQactLzZm}LWgx%tBCdb;_Sen*_pE#R z%fGwJqh~+jZ#^?>e(kr&f9)5G^rXAOOYQA0?E3Nh)Pn7kBLoW8F=Pc49f+E^?uUue zr6bZ@soob}SI;^=v9t8z9y<%>BL^S(+y9-y@3c^G_oH@w8M(5ZHh!_4?u$Ix_OAc` zPks~si&$3M_oq)!4mskP)OcLztH|OfO7g#)Pl^b0vlm4*$Fcoc=oRgLtwObG$<$Xz z)_l{fd$Qxko5PoN82Ij<Pq_54Oyzy$gBM)aQZ_l9=YNzK`CM;;ZG299O=a=#6B_T# ztW{_J(c|QMx<&uU#FV|4ogc8=J09+<cPV-9jD>+84j<L|@OJzCXDrE{%90-2;_H5@ zp8lvP%insuB2@g2)XT<%%dZ=Yn?HTM9)JJln$z}ad;9KG)OjxJ?)Q_Q`RZA$qpL)- zpnTO@fsQ9F*B5M8oGIkHhhw7X+7{<vok#9crPIXHSj>)XV*m4Oc78(otb2JE+UED} z=`)&e(0|Uu1shp@NlbVf-O+fXV#k`<%<HA!D!o4)V7JRTcR{eP&I~Rkr}NCqJ(jJ! zTPGCKTduveduoZ-me{q+o;}U7nDjxz&g`PmVY@$*ROj74ZLnbxbLm3;IWEb^9xE|^ zJT5MEKy|v{a%Wk~o6Gq4dS~qW@u<6I3&Xp|(`5pl?yGt8CVBCW<C7M0>=piar9tXT z(W^PLaysw33Hx8T{neyjIe3-x_l;hB9-VvIqLz2dPv*DQNjy__#IZ^9WrLTCP(=Ck zPiNn>+nT-E)jU<+@#LyL{`hyNkB7-iA3gHvl#$TFCehfhQeT=iZJgF?$BC~!@_m<} zdz|6n&!q(ym25x#`Y`e1=6N{|$%ofA`Tt~{<PdY@WFTvR=kZH1QA~SvA3AIeHTfC% zci*2`{coS$<vtN9zJ^uZDu8#Q?yEz4=X?;_->Wun!5aqKvyUe(w=y`^HqT3X{Sxl8 zud5|*b`>f*3(i#u3f%NFW$n%B=aN5v^uAq|renH|ZRwH)s>`c5t0YUsiVi93e9SU= zv2n{yXE8h1!lPC;7x&0mEbSHldF3hloJU_*UjMP^w+hqy)&03$Z%nrS`=`2p{Xf5? zpAIPoPWr-ytieqs9XXuq9oOrFVqD6_eNV^rR~JOpEm)<sa&1!noywP6^?s|!G%JHL z4Wp%uADMf7bRKaZ64#R`-P7~D@@I$g@)-|*TJ6Y~XxDOm>F<>vYiD=%F0x~iKQzDS z`9awZ^9Q_La*9SG7iZskxBR)L#>ExQ?k{vZ!WVU_Ye-&YUAX<>2hqtX%4aXXKm7Vq zZI0dFN8Rt)UwnV)U+JXZ>@cZk>m2XWkGGpNPX2gsBz(ga(+8#+wffPEzFz!ZB^WNd z!oqfKuUutr)x_&de_yEiYFnncZ|#-$hx>O`YJOop%yaB6JL~aXHJcoRnbHnU<Cov_ zM?r57_q3F~yH~_Nc2pO8(#o7&s{i<@lI<s@_-_k?Yw82+RsP#;Tb$Qp`zd*MSG7*h ziW3e#7Ux0@mU(x!&pV#VuJQZ^%X{vNYfm2fywumv=E*@#JGFTB=|0l7pKg6oTJK%1 zasT*l!_tp?ug5BOiAWtf`rt*+(uXGVqx?)OayiwlO*QJBg@U&2$vcu=`l2h^$f0X_ z@bP~AdH0V=uj;OezkTt2^2=4eM<u-vURO9J@^SX9yL@TWvbA6Qe!?8yxcrEK2){;@ z_?JzpGP7<q>c+F@FHTMTJz?K;{g1nTeEOPwO||UT;?LrLuVgj_g*^5yJ>Na|>FYB^ zc0Z4NKG_zpDq~^P<yyfj)c#yEEaOn))+*0qmk(%OVh_)GAaZ{3yPvsx`j<*4O9^eh zKKGN|Hjbvc4Ylqr>J!=R#ZI0u`zoX-CGdY$GQaF25x(=cr~2|woH}R0a!&TD=zVdM zKAIGCeK}Zr$GJk@d%Nzv-$zp7RWjo;L!W09?73*M`GB_QO@Z_5x^bBkT)QXC>^i+) zQ8M+3_Mu60GETV3%<7+LwY2w|#~ZcDPaZxzEPnp=BYCgwXY-y2F7CJdbi3l{+1(;6 zwa-^L7cA;NBK$6O>3im;WxEO-WfML8J@se5@GR3f(pKVSVInVH>LtB);)?Qpw-haJ z2HpMK{^7#?@bdV*3+=jRm**d0e^i)~^pVreJ=4?KUzYt#>CJ>RrHOwo*Hoz5zT|J* z)au9C{;V=`MRQxvqwY`bk7Q51n@}du{A-oi`MHv(wJV=W_1Uo6ofKpanJa$lK&j?S zskhHU6Kb2otsLIm6MuC0_5!`#hZ;Y<zJ1H>^R77gIYFB)7kqmzd+Oc3jn^LCoVjt& zgz}KXdu=R^p8mCKey4;E?~M6RmvFcI4+tncq4u@I+s#J7)lf6epZUFW{$<HS-T&HN z{IcL*`uuHPx{iNHdR3G(-}==Rvrnwjdm+uo_jjSfzdgD?r=OQ_UgI5C)}W;tS+S`3 zG`~e^{JiVEX14q`pT2+HxjwG+SXW~eyPSJp&?3`0MaowTz8qjpJ$?DeZE?5RS5o75 zq<DSRn|1x`rUxRA!UA`P+OzQA-X#6^>r+MX($vlT=j#feZ@cJR$olf@f}>3Kr+(}z zIJn~ea=p7!N7^b>Z9dh+elm@U-?d%1)XsEXKf8_CBmJ$6mM?CnYs9VK*JHkz7=8D4 z@2^L1x%b|m#Ots9u*-c<&|bIK&fm)>E_b+mH6YL4_5apaPmg>$`2FBj&FyzD$bbHp zU8b^G|4Uiw;mfx^oCv;q`{R~<U(Dv-SIYkow8D?!mlOMSJ$-gLcIQ7rv0D~i_&ssm za!zr64VzCZf*1KXzg<?nJMg)H&Cd@T#H@|lAMnO;CA)87UD`G^WzSrpcbWJ4tQPV2 z7;WMdd6(Ma9_MLyY0tXr?S2z$q}NZp^LNTUy{#*+RxH~7)5;|O{n81`rZ#DpHe>|e zyY9UsqiNIQg%zq2nJ+rS4k@3%u6n=yS?c0F&l`U?t<m#m7vFpKo??X|pS-tp{`#c> z$GqEiXKehq?_J?d!L@9v>^(-YCLfnBw+djL_uM&uvFpuQdzNp1`R4TDI-R9ERD&OP z+L`n@dN1U6`eA3lCi(hC&f-q-h1Z4G_l8ZHxaT?V!mBB^wzt07e2AE!|573RacA76 z-TL~+!n=~6KZ#`uTEnWkuU$3r<6}qZk|(=gOcI=8yIy}@LZ^7<m8aIPT<5KCR))G( zlP^Q$CVPEG+?MBmc^`i5EVNFpR2N+C_mNH7%US#4(dluX$1Z2>c+;V;vFlBB#3R>z z+aG__SYGA1%&q&&EM9;4MUTH8NIn=gdv9&!MgFg|*5@Wwrpx6A_Z6ugpLb0){?Yb} zzdr4Wk(R667x$VyWl@apw%&WkCik1!=i1~PSDHQ1Z|^bTy=SM(8O*C?U|=W$uLW3J z-dZFy_q5}Z?<-GlOiAABGQCH^2vob-tZg~%_GIO|pUxSkk^cSLet(+K%^KaG$iLq- z{!!?AE>(svOGS1(|9)^WZ~Xd)n_mB%_^k2}>%_GWRp(9bt$8Jq;#D;9S?cYIn;Abe he*TE~)^_CI|GLLn4qBRK;tUK744$rjF6*2UngE{KG6ett literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_window/120x110_flat_2_elliptic.py b/archipack/presets/archipack_window/120x110_flat_2_elliptic.py new file mode 100644 index 000000000..312f72995 --- /dev/null +++ b/archipack/presets/archipack_window/120x110_flat_2_elliptic.py @@ -0,0 +1,58 @@ +import bpy +d = bpy.context.active_object.data.archipack_window[0] + +d.frame_y = 0.05999999865889549 +d.flip = False +d.blind_z = 0.029999999329447746 +d.blind_open = 80.0 +d.hole_margin = 0.10000000149011612 +d.out_frame_y = 0.019999999552965164 +d.blind_y = 0.0020000000949949026 +d.in_tablet_x = 0.03999999910593033 +d.in_tablet_enable = True +d.n_rows = 2 +d.radius = 0.9599999785423279 +d.rows.clear() +item_sub_1 = d.rows.add() +item_sub_1.name = '' +item_sub_1.width = (50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0) +item_sub_1.fixed = (False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False) +item_sub_1.auto_update = True +item_sub_1.n_cols = 2 +item_sub_1.cols = 2 +item_sub_1.height = 0.800000011920929 +item_sub_1 = d.rows.add() +item_sub_1.name = '' +item_sub_1.width = (50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0) +item_sub_1.fixed = (False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False) +item_sub_1.auto_update = True +item_sub_1.n_cols = 1 +item_sub_1.cols = 1 +item_sub_1.height = 1.0 +d.out_tablet_x = 0.03999999910593033 +d.out_frame = False +d.y = 0.20000000298023224 +d.in_tablet_z = 0.029999999329447746 +d.handle_altitude = 1.399999976158142 +d.out_frame_y2 = 0.019999999552965164 +d.out_tablet_y = 0.03999999910593033 +d.in_tablet_y = 0.03999999910593033 +d.out_frame_x = 0.10000000149011612 +d.offset = 0.10000000149011612 +d.window_shape = 'ELLIPSIS' +d.frame_x = 0.05999999865889549 +d.x = 1.2000000476837158 +d.z = 1.100000023841858 +d.hole_inside_mat = 1 +d.curve_steps = 32 +d.handle_enable = True +d.hole_outside_mat = 0 +d.out_tablet_z = 0.029999999329447746 +d.window_type = 'FLAT' +d.angle_y = 0.0 +d.elipsis_b = 0.5 +d.out_tablet_enable = True +d.out_frame_offset = 0.0 +d.warning = False +d.altitude = 1.0 +d.blind_enable = False diff --git a/archipack/presets/archipack_window/120x110_flat_2_oblique.png b/archipack/presets/archipack_window/120x110_flat_2_oblique.png new file mode 100644 index 0000000000000000000000000000000000000000..e775b887e1a189ab98f867c34545b28b3d68d452 GIT binary patch literal 7969 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 Aj{pDw literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_window/120x110_flat_2_oblique.py b/archipack/presets/archipack_window/120x110_flat_2_oblique.py new file mode 100644 index 000000000..010b40731 --- /dev/null +++ b/archipack/presets/archipack_window/120x110_flat_2_oblique.py @@ -0,0 +1,50 @@ +import bpy +d = bpy.context.active_object.data.archipack_window[0] + +d.frame_y = 0.05999999865889549 +d.flip = False +d.blind_z = 0.029999999329447746 +d.blind_open = 80.0 +d.hole_margin = 0.10000000149011612 +d.out_frame_y = 0.019999999552965164 +d.blind_y = 0.0020000000949949026 +d.in_tablet_x = 0.03999999910593033 +d.in_tablet_enable = True +d.n_rows = 1 +d.radius = 0.9599999785423279 +d.rows.clear() +item_sub_1 = d.rows.add() +item_sub_1.name = '' +item_sub_1.width = (50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0) +item_sub_1.fixed = (False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False) +item_sub_1.auto_update = True +item_sub_1.n_cols = 2 +item_sub_1.cols = 2 +item_sub_1.height = 0.800000011920929 +d.out_tablet_x = 0.03999999910593033 +d.out_frame = False +d.y = 0.20000000298023224 +d.in_tablet_z = 0.029999999329447746 +d.handle_altitude = 1.399999976158142 +d.out_frame_y2 = 0.019999999552965164 +d.out_tablet_y = 0.03999999910593033 +d.in_tablet_y = 0.03999999910593033 +d.out_frame_x = 0.10000000149011612 +d.offset = 0.10000000149011612 +d.window_shape = 'QUADRI' +d.frame_x = 0.05999999865889549 +d.x = 1.2000000476837158 +d.z = 1.100000023841858 +d.hole_inside_mat = 1 +d.curve_steps = 32 +d.handle_enable = True +d.hole_outside_mat = 0 +d.out_tablet_z = 0.029999999329447746 +d.window_type = 'FLAT' +d.angle_y = 0.39269909262657166 +d.elipsis_b = 0.5 +d.out_tablet_enable = True +d.out_frame_offset = 0.0 +d.warning = False +d.altitude = 1.0 +d.blind_enable = False diff --git a/archipack/presets/archipack_window/120x110_flat_2_round.png b/archipack/presets/archipack_window/120x110_flat_2_round.png new file mode 100644 index 0000000000000000000000000000000000000000..5ae472dc47d0d6472fcf29e44277e5df8b005ff5 GIT binary patch literal 8571 zcmeAS@N?(olHy`uVBq!ia0y~yV3@|hz>vbh1|p+Reqdl=;4JWnEM{Qf76xHPhFNnY z7#J8tOI#yL+%j`g8C<Ml3W`#TQ%j2Vl5$e>QVvMQ&Szj?kN_!gNi0caFfuSS*EcZJ zH?UAJG_x`=vobQ?m~^;>fq_8)q$VUYH<iJ_zzT{C-yBvu1hN$*=T?*mmNYyVD5?Z< zBS_FWF*mg+kpV(w{D1$Ffq{V=BoUmPnwQD|CZ8(Cg8U&25)MkuOGzz4SfgiPvF^>a zVg?2U22U5qkP61Pb22BZY%grP_*|x1-?LLhwJUy2!H?Dd+yCu7EXWnjrzxZ298=W4 z^LL5eipum&#SO1k&3k!MRo5pZT`8Zzl7TDqZnORWpXXnToJug@n6fNt@!{OZCnLmX z#-=R&ta<qBFF*75BJXP7zFBoYJl{r!&s^$;*^k|y3N9?$E$HBXbH(qCueUP3xLkLu zAl`cStM`k-FTI=-kt=)o*y73CK2H3&bp4*|wK{vQ%>40A`giS{D<#{He!Nh%)a<|U z#%(D{xdL8uZ>GJnD=WKt*LD4}%hT4V&h<CiXyQ=I?q5}#xH36qaXyb}^zAoSzH}`1 zEf?Q={KC&A!rslh*B0-cbNsf|h0|Mk=S&yAmsUP|?X0}nalY*pSyi>Ax1MCz-71k& z-I&$rct3q*X_ff=+_~G%zSG+UlFN+OJZ3g`->2I(cRbh3a+0o<=HKVFlJSwPTF&c~ zM>oXMXV>{|Uw_W|)bv|d()O$HuU+l8{>UoJ=5;}@Lq2*Jh|irQn{iu8Z*Q*M_M4h7 zzhC{i^yKC8jd!=2^$7MHx^1;zO@D6?!@TYOd%SagtS{|I(!Tj}_2=yA%h%V~|NM6E zOhlZ+Txn6;*a~0&KODbLx&Pg^D?ehk%$^{b+g}!+vwOPzW?$})D^K!;yqN{9{lDB0 z@c&*hd*Afi?DwAS&Oa`E-)!!S_5FXGPYJE=Po4et_=NkJnkoie`997szwKNdH=S>_ zt3Ykp#;2vfPSpEE-dew7_mPeIKiTJRtXc4YKk`cA#kA{uU#d1;4c*@VXSsrR^zS1d zFML{SJguB9PCLt?cYpq_@ApjuMJAW*ex>+d^MufUyGIA!>D3B7-f5g%dHBIrrRS#q zjxW+)w{Gq_i{ENIRk1Z+I+yqB{QE9yyiK$Ajr&iV<W%kJ@>z41SJp`1KV9duCx5xX zYdtIZ&Aq#i+}Yl{`_z3EF}7_}^^9)k{F(Km)~YssQ~$;JwMpu7Pqx3?C}MHwzs~$L z{x?P+VyAulz2#f@?1++>>Gf^1`Of{fuxZWs_c&?!y71MnZLdDlJ{DW!68&HP)zP!n zD<AHk!WXh=%Te#Fh+9i97ED<7xKL_&>Z}{ux7b%}mp#ceF<5u-YR0732cA|<dDdJQ z#nYC#FJ|e{q?uu-TVq$}ti6&x@0#hwy?)_)*1z5NDfaP)qGYYy<_E32PgTx6erUUD z$h`C%t4Ff`4{!1E4}bph<eyln__LQX3ez~ZcpQGZRP|2j!_)je@sd&IlDnj2mp=WG z8Rav7`OhbwvwZn3ht67d`sx9p+=<h=E9YHJvi~Z3V&5mO%J_om6;}GKb}w!e&ig)N z$0W}ATW5HPOnRBI!_R-3_{$%!a%Y{Z+m>RLJI!o%W6{a%2NV8mJXAGfBG2_D(v{mR z{2%|!l!~v(KiPRhW3A4`H23|U;+LnttSOqHcYRvQ=bdL$PG>W#wY}|I`d;B(^uFX9 z<wvUSir%|u<}}|=`{nnAKV6*GpEc)X{qGsI<ofjcOAr6NZL%fBD?56Zj^edx0X^SO zr7f{MeJg9`IvvGhivwq!&HZL*IPG-f*}V44ow_qs7TkR+Q9oh-%_}OW6Y3`MrN6(_ zc(W$Yv#jz@s@2?me(8^!bINnec2BES_l*C2Wbf*8zDX-T<~FO&G^>cO%{k68Y5%%y zo*l~GE5gI=m#K&U-_T-pvePALLJ0TiS)WgzF4-Efy!)!j+_S;E=Irqj&%T}St7dk1 z<9x5oeNoGuy;pQTRQBH1sCx49!KF9%d9*jL{_?0GV`J2(nKrEU$KT&ye)yqdRoR!_ zUmpJ0{xZk*<i8@P)rzb)${FOH=Nat&X1PhqyyTrqSbXvc`;wQJCEi<~4|1z8tezhi z|HrmhT>jG<XQRl~m!H1T`dd>q^PYZx?#o}fR`PPocAm9+-K^F*;}DyV!M5~88}{y+ zlX}K3XIk{;5Wk$u|80D8nSP&2Q8(k>Co$)l%j_O?%ad%YE3It)&MztW7+Y2KBekmh z&(@`nRMzHhmhYDDIiY>^yKevaeV);mG}Wd_ZF$Y${ohd0({v+aZS6<rs;ZCsr!knl z+IHunXS(?Gm-+Vl{9pbwfBE*u_m_tzC8ZksMs1q;yyM_If!Utw?KR~eX1N5{7syv6 z%GcYPi@#p}vf`IgtzF!*^w0b+uRe&K)swqZWv1@D$D8*rUHSI4pYG0k{_#sKH>*jW zPIWaaPCe~@pYgtH-Sin=R%<(#mVL|IQzFOxOQoXpXaAQ!f7Wk%W4PgPiQ(*Mr@2c? z{>=XJ@2Pyw%FSoDv6@LlDs5$vxUDT8x75?LhxgLWoYZiy#}7|`Ir?z*mryHD{p;^9 zU)ZzuO>v6x^KSn4m%n`Y{_^j~@Rv0y(e>8iucMyW9}B&Ff4fcH_Mb1NL}WQ!Z}Y5^ z58$1Yd-O<JjpZCO@A+rB^6g@lyuUZ^#>4~9U*7$+lGngEc;Eihb1$6{@0~hFWtxJ> ze1k(z7f;;xHsq>w=xpU&v0wjcrv5XmnH1+ge_8&qX$j5S#U`JfQzm=U$7=51DRp(f z^j{uHv#j=aQtJxH?fut%$nDN{o4Vb<UX=DNWh>2H@ua*ptL6IoU)yvNmTfw(^Y7Uc zrN0k1r@t)Nw88gB|F$nTPo<x}|FkPId&WARbGe)ImWP*#nVRjBt&!TdrCY^i+H8h3 zFHdpYOk<hES><Opeaeyc_44bly}bJ;^4GH;k-uVpUG&?nm-E_e)8<XxUtT@Y&n)?K zyrk%d)!Mel$K3jE<;3Yv3|XDKcbe#gbNinDK2l<wT*|wuztD7#b_l!YK7Svck69Vg zDbgW+%zWyLYfKC_PA;{!KhJ!uE-UvqZ!_b{<mJy^dt0Y&SJ@ReZN_=A)6XUx4BI{@ z#!_;+^5NiFkxb`uELDGAE90`?oDy?ukynx4I^m0XCTG1KKR@-Ypnl`pyR)0mUy^Ts ze<^+P=QGl4^)9cz{NJRx-sgRMO?I^76W@zo*Uh~5t@ewreB-q|+b8b1+QwaM9WqP% zH@hwVx6_;XUDW0yo80wvIxTm8I+N%;XSs;k>kO04oHqhEQzJcQb(+cCs!3z6O<{TD zw9eBlWru5y5dW-4YKE3&5i=(5Onorv=J^b6>#P~kk794!J#;?XdR4`?ZJ!M`r~hM9 zvyZr?yyP{*?r+O$-bK&*_u}>8*EuTF(i^UOg}0m5`Y&JnF8Teo#knVRS9-{?)u%{* znf&>*$!68}7p5(~Sde*T>e+y@jMN)dCl4K8lu=*#zIC0fmWHsW)9T3+R8HS`6MJ@= z<+VNG#=klibN$|&TRl&;@|eX&XZ;u6*4oOBx%Z6A?<~xFcl~1Y&3O}+GcAdV4wRnw z@>2NkY3hBsc2(+O)7u_a%wKv(<7`B7_7>T+Tlde*yZ`t2&DACwl^NM)H>L%jbcmX_ zgqhEtcmAdYHy`-FQT%eSW0&gutH)0ld+!SMn{>GI?p@!FUK@AY&Mqu!3R2eJQEAZP za`f<-Nf&)?>TL58wmyA=GrQAWu`cOER+?Lw@#WeTb7OzSY+CW&S#Ec2%@?mVdW^yc z?7Wt{J(X<X{c}j@<Tt-Eh021AY6a<ori=5#<IdzHd|q(NjmzWVhvJm-;<l$x`?vjj zeKECa?;kt$Cr@*x{j2+`wRV2Zv}Mej&%a4O$-Zwx`+0qJGiJY>z}U|URa5(pnJM4& zwDoUtn>Oc0`pMLjXO7C~yR%PYnQa`lyv$5Y%J`a?*Yu4yua=cvxa|J?$Sa{8Ocfa( z?Ij=2$0qKZVX0$yZe7lV<>lwQH<a4$F~1-a(|-5O;fnuUQF1)qZBMf%)<wuHxw%p` zU|ICBh#(&JvLlHfeREH?+HGq|Ym~YDhQ)7|eAK7J_eXD?+uT;R&aLU;*(b%j*G*om zsL7Z0^pd*cuM>~fuCMW39-NdbeZ_1a|CTGhFSeaLkiB_-;^lca*XD>x==s0>a{FBG zn}>$q3$`7;x#8k7|8Ei%)hcIi-0qorsl>$3oqPB1%}X@??opF2Pt&^HXYN@%SLbX^ zl1a7Cm;CedZ?^qymbIR6qrgMQ=HI4Cn&09xYs`dJs{|~1x-O%NWy@9%d$!A~u1u~> zzMb?);((ile(Ro2gGcXlZv^|yt4TgAc=J~B>dFf`Znm5$;+#LHe3^Cgw4BQ`*BdjQ z#M&Lsdg(4dIY+yT<M)Y0x83iZUbI_REG0^Kb;<&l!xy$G?Uhie`ruWPaY_5;^mDyE zHzRvzE#;Kjd?MvU?1OJb$&aI^n0uIatvWS@_u1X9?b9FMxv<+TZL`AB?ZLT|T(53a zmznp*JG}Iz>x<I8byI@xp3dLg+c$lA-Q<Ws%WHyq7Z(I8>04zjdF8267vAyL+U{kI zw@!B2K5m;CH?j@xygIJ(@=1!auvO-jmZCXg&3x*!wmf>4G&Sv8!=F!|l(wg4>a)ki z-YjOYz7jg~wsn$7#-62KGjFW6z5Mb@Ur5f&?R%vIN^4fe|B>jJ#w%jCN9WD&Q{`T3 zCQTMPd;Im!tqW{rE91X!Q|g<uz5cYh^tX-{7bV?_$oxY)r`~)QyDs!ocSG6<Kj~RN zdhVHr)g<R7mG^85d(M^l?cEcdx${l+CAI8snQgWX*y>;F6jb@=(mnOyz@&x6H8pei z_;%dh<7;rzRipaCQ~gsb-lgt7AufE{`eTK7GuIL4n;i!$XRfm_OL^bdSNbfgX2y5( zI}2lPovtjlmPifQ>v!Ga8js($HrCt1h08NI-9CO>;XeDy%*GuDEURYt>*;(HbUWN| zGy2BeXS@duZtC82Zf83Gxzl^|&7-Y*f2JDWx_B|r%Eso*jy$tHd}U9#ZKm06dg1nS zPQ*P&UiX`yR|I=)X)C@yGewl8^x>wfVbkvlx#%xvG!{Er)VOrQ?;Z1{Pn})<NZ{aa z5#AHU2Xn6Ge7vT))N^A`Ud;)$ZCjQ+b=fnmNzeX?s5#r5ztbmW9WFTDmGfA7|Cy@g z%a^}=d3pKIq_E%~)5)6{_xI;`?ki4mb>A4rb?x+#1)=V46(`-qrGDL<-fLB@!`8=g zf`5v8nNFtNEw9e|IrnBuW|!2is<XWq;w#ubiR(r8U6E&puN-sRXMW|#)1z`M>U?@a zZw|Nfch=sTtnUAbXW30|_Y*&YUa^1teJZbJ{RQcFO{Z5XSQ$1*oj-l8;3Zem?c=Pq z&2CILUmx7#=CgCw?UUQ@O1_IZBXcX}&DL#^NeYrjW`tP(u4iyfvb457ZO(coPCxEO zb;9-k8BMC@g)+ZS-FvgqKrhwNzq?{(+@hZh8(+UZVPEm^%iW$$CuIC8{r7sd|J%1d zedp_(MaTA?wOT1*RCnWf#`<6pBc+E4jY|XaL$9RBH?7;K@-R~BX8FedU9Nvsc6|x? zzM=b#Z&Ic6Ay&hv!+Q@4>DjV6*Gk823SN}x{_SY|?egE(9w%3~)g<Pt@VtF<$Lx5S z$0;4P$e+{w1n)iJKkYTy-)74`gT$K?<-SQ+=-m{X(xYh_9M87RC$h@cE$QU`q-S}P zo&?!`3jJ=dP4$)6wbOS!b=IrI2r$b{KPYt8dg8o{6VnUw{cA4nI<O|Wzto~OebL6! zd;2W145j(}3%O3Eoej6EG|v?~$o#wMNJ?Iq;Tx`9Dk8H}ryZK5n-*|y$_`!gnbqD^ z2|jOC)V_tyDK(6C6kq?uXxl=y+iKyyI{7|37BqF1UTl0+`TRo2_Z`<?cO|>qT?qW! zmiFfl-_=8Gx9{0W#LO?6{^tIR7~{>lho6@{JM!A_%~igf1d9t>YX7)7POJ5a4qABN z+1#W4t>2YrH46lOW6zYI{rwV~-1;pCr{9o&R65n!PJNoWij7l?Yq!PMsEK_|i}Yfw z7IAj%6HYlMCBO9Roqejw`+h7t_vD1%&T5J5M;U2Waj$0I%1M2H_|8TLYYWZq-VbCK zIwXACT^S>yru*jMZ$)l1%dP4wEo{D)w|)x#94V##vO?nNgItwqG8{?@F&8c$2(^)q zIyp0i_2Nyn>#l6l(*Hd5ZijC?zH7^oM+K9%yf97SxShJJSg>Wqj42^!!g8OQsq*e# zr<3zNJ@EE@-R)<Ot+VlaxKmhVqnGE?x5qZ#coD3A!*!kM#yyD-n?4D@p7)N~!LPL6 z``^L`x2CQ6`mlS8-OuW}!--kTFYMVnlix!3;)k^?pEqi6tCGoi<k`gbt2Q+D`K;tD z8=*%}9k{~ek{|p1z7d?c?9<z`g?IjK54ij1`I#xtl)w3IQ4Z9<uDNl~LMgq+a$aG4 zE4jZHJ$U3Q@pMJ<;oD;OPDI_hvAuMM@RQpow%K~cFX4Z=_iPk*wPb##$-0TU{C|G1 z@0t_H_*sQ@OZUa+Z_h28z_ndMx-@NduJP28O@_BupX7?r-B@!w@9Xd6Gk)ji*?O;! z-+!-S#arFnZ_$S}d(Z0??G-w0{b2|HHtoKBRvWL(SXZ`Z&Kp(hqKF&LmaoqDsl7D6 z{Qc$Yuebj+XDZL$x=!rdrhC6M<`&o_cvhHgzdrpf>p^X~hc}cLu0E);e{-GLj7XtL zG20xspKL3?mHth&XXjtG7JHVvOZ9K;nVd1pw$}Pxw{XAN>o@CfJ?gHFpZr$)yGEz$ z2Cg%YzrS_JS*4s;Aic8nug0y}*Z<FppKJN&&8a$;BllQiW0pPrbLyVKhMC9e4BOs| z+DU$1JJ<X7KKakbJ>^o~1k9c}_u!>@rnk1|{m4^%UnU#7cVoUq{j@i&-@d%NGw)09 zTled4)F*fd@cjs{nV_kc9FnT}<Vjcg{o;dl&9*<(`4{g0@u=G-_-O6aV~&1T>sN7? zP3$&$aq3>P+O(c&GFlIgH{7qa@VCDi{^qBp(z(30Z_%8O4^Pkh((?UM<<IkM)6eeR z8+qV;xbK_awc6H`W_;+b>t1d0NL~5)^Pk;<;Xk);exto|8tZ;*vzZsa@BA$GP4-}l zeYi<;{{E=q#CX5G8;aLySF`@CNtG`CUAu;Vy2^c(xworqw&<5_h<v_o!lCK$vHx;Q zxc@HScPF7_MZu4gi>1@F<ZOcfR9tyeC0XKC6r1uSfB6wD%daLMf&?bI3GfGQ==NVX zzwVdj-|O}NzoxBxWFJ;jpf{<v@A)jBx3ZPdeColwvp1EqEHqrsb7S#M(@jqvZ+GJl zGmd6@d*#+W&xo^crvH7zEyQen*L?cV*Pp+A(by|xw*G)gf8CoquN$7szHupgo4}Tt z>otA7R(=zxZQxCM&NtymP>|#KeU34m$sYrZb{x1CCv^H~eSV0iVB%SW?bqvnU7lZ* z^zp+~-fusD?%ccd<{pXTn{8fQc*#(@Vj9ohxI;yU90KP@DDmZ#&62bAUcSt@>e!id zOXp8F_Z&WRoX28T{F0@r^Z!npuR7m0Xp@Vyj)$Ff%!cgD`=&Yjj-1+av|QMi-MMwq zH?as;p|=;bjs84d5i4{rT*=k`$KmVugt*%`8fHClJ`i=hSpA6W#aQW+N9M{{?cq3n zOq!#&yM6PCqLv4xNzx|2pGKZdvzyenl-<hse#Rf0-Z$wzY1h32{zZC5XU%@HS^swP z?YjJuPqn)?+_#mwaWHqSZ0)ZOi=XXPKR>0;>-BDqU3BzgrN_*@s$tXn7AkHmjQz5w zM_wZT^yQhgJ2d`;2di+ad~8seIq5^YeBB97ccDX<UC&(DF+tt&VP-&sN|c}3XV2J^ z=35WS_s+lZ@BEK#%d5X$J^kfiU6jz@ppRwVH5oUa^L@CwI{fDzufi!$7SGp^{<-OW zZs|lvJN0QiCoY<4R4&lJ)O>Ae@U4_v75A1G=*t(LWO(?~>5P}vGy$&7Vx7*DGTEH{ zcU?HZYTLI@$iKg8hG6K2@?Y;_f4zM^f9Yl6-?>)=`k(A_N;~`f{QT$j5|eID&$!*s zUv&G&?o$i4PmB;KSjUhRP;?+_;<_IuN|%nj=1%p#@Va`|@rj+K7x&m%FdsSiXtsI& zCr1sFr0_o<S`Q!An|^$y_D2tslMB8-eZT*t-GY4W7nSjG^Oh+2331y@Td`b1Rs81# zd9P(NA}l6cmil7!fIsw>)uv<G7nRqBsHbi9|EPTYXqx=IG{%A#b!?OSwkubjXLb+Y zpf;J+s!o9av{iZIo1aP6PfpLi?dn#z{if#0C;4&@0{nJ6$#`C~zRT|SAhzb}6D8Yg zosv?l_AzIe?$`a!^)r}eGO018`uDfoX^)+)<hc4juF|VWUc%huyPo|V@2TnWb(QIx z_5RQJYE|_3r|Qhynvz{7Ld@TDX&sX2`Een;)j>sl58GbelT$Jtxpu6{P?edLBxrwW z#pW#r${P%)**>@)U(aiO`Ab=|*`J&zrBfPt>K^g1_o`kv-jH+MiRZ|%!>QleF8Zxe ze(xP`x2raHL9nmR3@#<7^UTXVmaV*7Clu0KuD!K$YKhkt-?hu0J<YP1^g+YU?4r?O zyFZgu=iNVTuwfB%=|cTEF3HCpD=~ije(xR2LDlJk%bjH{Z!Y8G>z!fu|Ig=|Ee!7- zPnQXJy07NVo211%j!#<1u~+!xl?JIVMX%<}%IUoCChULV_E(dB<=|D$-#2>kd35e+ zi(1|-KbhZJC-F?#5yvLYmknMnLJ{TDKb?KkZfo{tSMyYP$CIo2_~YN5J{~46ee}qu zQ$|7yn?z&3N_}b8v~gOmea|ea^L?pu-=3tl=dT|ua;<t&`cV0Cy6w$|V{Or_^FMK` zH16n_yqIgjq`sv)x3E-2A8O26o$+b$uiBrc^5(P4^%c@@C+J#SY|QG&2^4?#v}O8_ zT$7J1sq8N@<@#-|HX53J3At6F8k-S!|MZEl9&X957b+9GCY@E!KD)0(ZgzkCt!;+M zD>s^IsIux>UiH3kdd21m!g7h7;fo#nm!6f|^g**r``W|KXHO4k=I%MMT+jZ2$a??1 z$Hecd3s!{J<^-oKUs?KH|7Yx1bD`7hO3i{*Gdr|r2rY3=@vh*lss%;)RRya@u6x4T z*I69Yyb+N3Sn;!e-RFSX<x4kQY24^MQIoerJ|iVbFkWC^!NC=eQ*F+lc9*d{%5(nu zLD%jtc6)Mn&6m2redY~~SIQsk{uq|a-D#{~zPI3oppvuT-j%s^Rr8vJdaWv2?lo1Y z&Z=md6c{T|CCwLa=xKJz<}XNl|Jmj7H6Q;n{I&aI=>H;Bz+=+JX~iq`>Y@cyCFSS* z;F`9JZ4cYh_o?1n^nS(ZKU&2sx_tT8i^}VL^*?QUX=f5&+aKGNul@D+hsAkuTz?N3 zG#7q%Jh)MBe$Is{4Urq39%k3K@8G`A=Dq1&?3MK&7YK8II@Gvz*YuA?6Xhx=>i@o= zxzB#Z`^ok4w*$?O$^G0Id(>9UEhI%jP3F0l#O0+&oX<br#5|=qr>VU4#kG`&KQF1N z%YAwvB0qV3v-h%IxyszCiQ9d*>)${A+pzTG-pjFyT_RG4jy`zNv-F|K{3t)uid;^0 zYg3K-TA`qAd-9HCm%iwVHgf1%9(=rCf8PCLuUB{1gx|jSKKbP;-=mV=2d^uf68Sj$ z)?L1|Y1!H@em`LjZ(M#vK!ji8mf4F`Zwr&H%+dS!?zx>l^i8o=KmJkf<I~smBd6bZ z*}r!Gi{*a8Qx-|Zud0rFqZ6M#?ej_XdH!a9v>qOq7^rh7dxC|H|3)WazBO09jpniW zO|;vQ#Js#huJnBV<651&62bFl?0q-m`$kuRFU@bQm8?2#er)t`+qQCg!NrCj;j+~) z1eO<6+*_AZ)v@LYi><H4h1;LLPU>0S7`%l4-Q#zMe@E=f{diAZ?YENWXRp;(?#+*T zkIiI_TX~|b!Y<>ZSF=u3%bAdvf4nC(l;Z`bJ#le1dvu|u=Za?T7PlnhnJWDJ^7|_8 znf=>Wa{G;&oZ+t-%KLl6@9gn*yind}#iG8;!{%XJ*e^SUj8)v?R|MO>O!=~@b6(1C zryP&vEzX|{1D9{PDAif@e6{SP$+}l<_w)1rjV&#|`ug?e9hKkRY82$$)x_o}luVgs z^5sgQL;Q5!G&P}<_GjC}FHNqK*NA>Gr66P8^ww<`CIr?5*2V9sz4`VsUs6E))kmPI zBGvUVhbJySpl+Geu<Emu^bS4M*u{7EaIMMz;Kkl?_qS6`h4huwaEpMt+UVOEao1lr z2YZ!CoqM;Z_U2vFYf(1R#?!<<^=0jt&c3`NGXA@<VDnPO&zt9zG=7lh(&D?hM6U2; z2m2BYwWF^yAH=@MwW_VS7%v|y|9sz<J$GkIo)*43bKbSK!?(ls_@;!4f8k|s{}&)o ze{b5K)9F2m*Hrbb9i~pwvAKBgDf^sF+UH-p&7Q|D_jC8JJE7~Xl8-v<W$ssY*Ssj@ zw{v2aMb(3*O;0l)z2#c$`)ZSZjM36xX0xt;-Sj}@QCQ&a(EIHCw>L@uEq$seUYfd@ z`+QyD^KBPv3t3-&U2v4i{?w0M1qWB$U#fRk>PTCKs?DdG*iWWW;k&jAm)e=m>u0wS zd!)aW(ej1)`G7qE;xTNC4sYAFUHa?M+oDzbRi)>J9TM%YSX$M4t@YhoCI7}{s~6s> z>-y(?^>pXc=J!pjgY$MTw0`#X`Wuh5@E31SwJqQJaN_b^+aG7fzRb1S?-uudX~0~D zFRgs*V&nL1_}YG`?#ifL_+2^HUsz8rpyEm3a+jHHyXL-o>o&Lc!;`~@qu!i&(|o;J zvq$!#*c7R-NgvC+b}X;7e$}C8aO#9wz>evHa+Rt-JwES>lecpHl)J~R=yPz@>?_N{ z9(Ue*mVJNE-04oU!noF5VO-=|8L$6v5$CDC_D53_mMm0{;_SDHpI#$AYkKdedF=1F zH|?^Kjrn3${rkaDiyEEWJ#(iyN$N}9bL;N?UfO@UFT+&BGGWS1H{tlIRm`CeD($|Q zMNL_Ivbt*axpg*gjt6x`8=1~~QTzDCR*_!{E$^9<Cv5CHYc{Jwu*&x1tqfJklhtwZ zQNGtMFU~#B+|T-R5?_(&yceo_e}77<S!!YCcTZK}XjH(X>WE3A_HtVs-Rq=djhZ$+ z|9Vtd=c7{Zt{V5fCq9=JJlTC=PtKI@Hb)tIm3}|qKimCn&wjf-dWQGTMBMW{vNCwj z!L~~myh=VU4WIOJi<F-ABknG}-*VF`Ccb}LVRLF_+@zHkmG-6`zWn<|h3$uk4Oi3h z!{fd<?|t@KZQ~x}{_UFXJ0~Uk-&Tu%wEg0)PkUmd<tq2Zy=G5Y6yv+C_ujF|{bu&L zHaW+YW>56ndrWxm+36K)%Ug?t=AL$3Qoi!^#+2l}F4KDyj0$;tLCYUvJY5_^4k_z= zv{~D7+U?28cRy=0Oe6jKx9$Ekp_?_jKaqdGY5b$m_gtzBUzUpOcwT;RF>n0(hnrsi zocOHr5bMOX4^`()@2z<ylHye~@mcEaiklffG=Bbw`1b3_zxvxMaYyZy<|;BUFfe$! L`njxgN@xNAZGbB& literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_window/120x110_flat_2_round.py b/archipack/presets/archipack_window/120x110_flat_2_round.py new file mode 100644 index 000000000..3d0fd325f --- /dev/null +++ b/archipack/presets/archipack_window/120x110_flat_2_round.py @@ -0,0 +1,58 @@ +import bpy +d = bpy.context.active_object.data.archipack_window[0] + +d.frame_y = 0.05999999865889549 +d.flip = False +d.blind_z = 0.029999999329447746 +d.blind_open = 80.0 +d.hole_margin = 0.10000000149011612 +d.out_frame_y = 0.019999999552965164 +d.blind_y = 0.0020000000949949026 +d.in_tablet_x = 0.03999999910593033 +d.in_tablet_enable = True +d.n_rows = 2 +d.radius = 0.9599999785423279 +d.rows.clear() +item_sub_1 = d.rows.add() +item_sub_1.name = '' +item_sub_1.width = (50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0) +item_sub_1.fixed = (False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False) +item_sub_1.auto_update = True +item_sub_1.n_cols = 2 +item_sub_1.cols = 2 +item_sub_1.height = 0.800000011920929 +item_sub_1 = d.rows.add() +item_sub_1.name = '' +item_sub_1.width = (50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0) +item_sub_1.fixed = (False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False) +item_sub_1.auto_update = True +item_sub_1.n_cols = 1 +item_sub_1.cols = 1 +item_sub_1.height = 1.0 +d.out_tablet_x = 0.03999999910593033 +d.out_frame = False +d.y = 0.20000000298023224 +d.in_tablet_z = 0.029999999329447746 +d.handle_altitude = 1.399999976158142 +d.out_frame_y2 = 0.019999999552965164 +d.out_tablet_y = 0.03999999910593033 +d.in_tablet_y = 0.03999999910593033 +d.out_frame_x = 0.10000000149011612 +d.offset = 0.10000000149011612 +d.window_shape = 'ROUND' +d.frame_x = 0.05999999865889549 +d.x = 1.2000000476837158 +d.z = 1.100000023841858 +d.hole_inside_mat = 1 +d.curve_steps = 16 +d.handle_enable = True +d.hole_outside_mat = 0 +d.out_tablet_z = 0.029999999329447746 +d.window_type = 'FLAT' +d.angle_y = 0.0 +d.elipsis_b = 0.5 +d.out_tablet_enable = True +d.out_frame_offset = 0.0 +d.warning = False +d.altitude = 1.0 +d.blind_enable = False diff --git a/archipack/presets/archipack_window/180x110_flat_3.png b/archipack/presets/archipack_window/180x110_flat_3.png new file mode 100644 index 0000000000000000000000000000000000000000..228455187596399ab26b4d021823258567da58cb GIT binary patch literal 9492 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 literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_window/180x110_flat_3.py b/archipack/presets/archipack_window/180x110_flat_3.py new file mode 100644 index 000000000..3ae2748a1 --- /dev/null +++ b/archipack/presets/archipack_window/180x110_flat_3.py @@ -0,0 +1,50 @@ +import bpy +d = bpy.context.active_object.data.archipack_window[0] + +d.frame_y = 0.05999999865889549 +d.flip = False +d.blind_z = 0.029999999329447746 +d.blind_open = 80.0 +d.hole_margin = 0.10000000149011612 +d.out_frame_y = 0.019999999552965164 +d.blind_y = 0.0020000000949949026 +d.in_tablet_x = 0.03999999910593033 +d.in_tablet_enable = True +d.n_rows = 1 +d.radius = 2.5 +d.rows.clear() +item_sub_1 = d.rows.add() +item_sub_1.name = '' +item_sub_1.width = (33.33333206176758, 33.33333206176758, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0) +item_sub_1.fixed = (False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False) +item_sub_1.auto_update = True +item_sub_1.n_cols = 3 +item_sub_1.cols = 3 +item_sub_1.height = 1.0 +d.out_tablet_x = 0.03999999910593033 +d.out_frame = False +d.y = 0.20000000298023224 +d.in_tablet_z = 0.029999999329447746 +d.handle_altitude = 1.399999976158142 +d.out_frame_y2 = 0.019999999552965164 +d.out_tablet_y = 0.03999999910593033 +d.in_tablet_y = 0.03999999910593033 +d.out_frame_x = 0.10000000149011612 +d.offset = 0.10000000149011612 +d.window_shape = 'RECTANGLE' +d.frame_x = 0.05999999865889549 +d.x = 1.7999999523162842 +d.z = 1.100000023841858 +d.hole_inside_mat = 1 +d.curve_steps = 16 +d.handle_enable = True +d.hole_outside_mat = 0 +d.out_tablet_z = 0.029999999329447746 +d.window_type = 'FLAT' +d.angle_y = 0.0 +d.elipsis_b = 0.5 +d.out_tablet_enable = True +d.out_frame_offset = 0.0 +d.warning = False +d.altitude = 1.0 +d.blind_enable = False diff --git a/archipack/presets/archipack_window/180x210_flat_3.png b/archipack/presets/archipack_window/180x210_flat_3.png new file mode 100644 index 0000000000000000000000000000000000000000..354e9be9888608fa87ac0ec7787eaf25c2482ea9 GIT binary patch literal 10314 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 literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_window/180x210_flat_3.py b/archipack/presets/archipack_window/180x210_flat_3.py new file mode 100644 index 000000000..df26b7a54 --- /dev/null +++ b/archipack/presets/archipack_window/180x210_flat_3.py @@ -0,0 +1,50 @@ +import bpy +d = bpy.context.active_object.data.archipack_window[0] + +d.frame_y = 0.05999999865889549 +d.flip = False +d.blind_z = 0.029999999329447746 +d.blind_open = 80.0 +d.hole_margin = 0.10000000149011612 +d.out_frame_y = 0.019999999552965164 +d.blind_y = 0.0020000000949949026 +d.in_tablet_x = 0.03999999910593033 +d.in_tablet_enable = True +d.n_rows = 1 +d.radius = 2.5 +d.rows.clear() +item_sub_1 = d.rows.add() +item_sub_1.name = '' +item_sub_1.width = (33.33333206176758, 33.33333206176758, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0) +item_sub_1.fixed = (False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False) +item_sub_1.auto_update = True +item_sub_1.n_cols = 3 +item_sub_1.cols = 3 +item_sub_1.height = 1.0 +d.out_tablet_x = 0.03999999910593033 +d.out_frame = False +d.y = 0.20000000298023224 +d.in_tablet_z = 0.029999999329447746 +d.handle_altitude = 1.399999976158142 +d.out_frame_y2 = 0.019999999552965164 +d.out_tablet_y = 0.03999999910593033 +d.in_tablet_y = 0.03999999910593033 +d.out_frame_x = 0.10000000149011612 +d.offset = 0.10000000149011612 +d.window_shape = 'RECTANGLE' +d.frame_x = 0.05999999865889549 +d.x = 1.7999999523162842 +d.z = 2.0999999046325684 +d.hole_inside_mat = 1 +d.curve_steps = 16 +d.handle_enable = True +d.hole_outside_mat = 0 +d.out_tablet_z = 0.029999999329447746 +d.window_type = 'FLAT' +d.angle_y = 0.0 +d.elipsis_b = 0.5 +d.out_tablet_enable = True +d.out_frame_offset = 0.0 +d.warning = False +d.altitude = 0.0 +d.blind_enable = False diff --git a/archipack/presets/archipack_window/180x210_rail_2.png b/archipack/presets/archipack_window/180x210_rail_2.png new file mode 100644 index 0000000000000000000000000000000000000000..b7808c27cc7818c683155198e32fc74b4ec17d73 GIT binary patch literal 9362 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 U9}Ql>z`(%Z>FVdQ&MBb@06aq9s{jB1 literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_window/180x210_rail_2.py b/archipack/presets/archipack_window/180x210_rail_2.py new file mode 100644 index 000000000..d9f2cb89f --- /dev/null +++ b/archipack/presets/archipack_window/180x210_rail_2.py @@ -0,0 +1,50 @@ +import bpy +d = bpy.context.active_object.data.archipack_window[0] + +d.frame_y = 0.05999999865889549 +d.flip = False +d.blind_z = 0.029999999329447746 +d.blind_open = 80.0 +d.hole_margin = 0.10000000149011612 +d.out_frame_y = 0.019999999552965164 +d.blind_y = 0.0020000000949949026 +d.in_tablet_x = 0.03999999910593033 +d.in_tablet_enable = True +d.n_rows = 1 +d.radius = 2.5 +d.rows.clear() +item_sub_1 = d.rows.add() +item_sub_1.name = '' +item_sub_1.width = (50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0) +item_sub_1.fixed = (False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False) +item_sub_1.auto_update = True +item_sub_1.n_cols = 2 +item_sub_1.cols = 2 +item_sub_1.height = 1.0 +d.out_tablet_x = 0.03999999910593033 +d.out_frame = False +d.y = 0.20000000298023224 +d.in_tablet_z = 0.029999999329447746 +d.handle_altitude = 1.399999976158142 +d.out_frame_y2 = 0.019999999552965164 +d.out_tablet_y = 0.03999999910593033 +d.in_tablet_y = 0.03999999910593033 +d.out_frame_x = 0.10000000149011612 +d.offset = 0.10000000149011612 +d.window_shape = 'RECTANGLE' +d.frame_x = 0.05999999865889549 +d.x = 1.7999999523162842 +d.z = 2.0999999046325684 +d.hole_inside_mat = 1 +d.curve_steps = 16 +d.handle_enable = True +d.hole_outside_mat = 0 +d.out_tablet_z = 0.029999999329447746 +d.window_type = 'RAIL' +d.angle_y = 0.0 +d.elipsis_b = 0.5 +d.out_tablet_enable = True +d.out_frame_offset = 0.0 +d.warning = False +d.altitude = 0.0 +d.blind_enable = False diff --git a/archipack/presets/archipack_window/240x210_rail_3.png b/archipack/presets/archipack_window/240x210_rail_3.png new file mode 100644 index 0000000000000000000000000000000000000000..1201622abbc396b154e0d9f15fc0dc4a242833ec GIT binary patch literal 10360 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 literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_window/240x210_rail_3.py b/archipack/presets/archipack_window/240x210_rail_3.py new file mode 100644 index 000000000..4cec930b7 --- /dev/null +++ b/archipack/presets/archipack_window/240x210_rail_3.py @@ -0,0 +1,50 @@ +import bpy +d = bpy.context.active_object.data.archipack_window[0] + +d.frame_y = 0.05999999865889549 +d.flip = False +d.blind_z = 0.029999999329447746 +d.blind_open = 80.0 +d.hole_margin = 0.10000000149011612 +d.out_frame_y = 0.019999999552965164 +d.blind_y = 0.0020000000949949026 +d.in_tablet_x = 0.03999999910593033 +d.in_tablet_enable = True +d.n_rows = 1 +d.radius = 2.5 +d.rows.clear() +item_sub_1 = d.rows.add() +item_sub_1.name = '' +item_sub_1.width = (33.33333206176758, 33.33333206176758, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0) +item_sub_1.fixed = (False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False) +item_sub_1.auto_update = True +item_sub_1.n_cols = 3 +item_sub_1.cols = 3 +item_sub_1.height = 1.0 +d.out_tablet_x = 0.03999999910593033 +d.out_frame = False +d.y = 0.20000000298023224 +d.in_tablet_z = 0.029999999329447746 +d.handle_altitude = 1.399999976158142 +d.out_frame_y2 = 0.019999999552965164 +d.out_tablet_y = 0.03999999910593033 +d.in_tablet_y = 0.03999999910593033 +d.out_frame_x = 0.10000000149011612 +d.offset = 0.10000000149011612 +d.window_shape = 'RECTANGLE' +d.frame_x = 0.05999999865889549 +d.x = 2.4000000953674316 +d.z = 2.0999999046325684 +d.hole_inside_mat = 1 +d.curve_steps = 16 +d.handle_enable = True +d.hole_outside_mat = 0 +d.out_tablet_z = 0.029999999329447746 +d.window_type = 'RAIL' +d.angle_y = 0.0 +d.elipsis_b = 0.5 +d.out_tablet_enable = True +d.out_frame_offset = 0.0 +d.warning = False +d.altitude = 0.0 +d.blind_enable = False diff --git a/archipack/presets/archipack_window/80x80_flat_1.png b/archipack/presets/archipack_window/80x80_flat_1.png new file mode 100644 index 0000000000000000000000000000000000000000..8568fac888efad1e5a91d2074e9553a0e1396344 GIT binary patch literal 7291 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 V#oH@87#J8BJYD@<);T3K0RWcJ;y3^R literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_window/80x80_flat_1.py b/archipack/presets/archipack_window/80x80_flat_1.py new file mode 100644 index 000000000..caf2980b7 --- /dev/null +++ b/archipack/presets/archipack_window/80x80_flat_1.py @@ -0,0 +1,50 @@ +import bpy +d = bpy.context.active_object.data.archipack_window[0] + +d.frame_y = 0.05999999865889549 +d.flip = False +d.blind_z = 0.029999999329447746 +d.blind_open = 80.0 +d.hole_margin = 0.10000000149011612 +d.out_frame_y = 0.019999999552965164 +d.blind_y = 0.0020000000949949026 +d.in_tablet_x = 0.03999999910593033 +d.in_tablet_enable = True +d.n_rows = 1 +d.radius = 2.5 +d.rows.clear() +item_sub_1 = d.rows.add() +item_sub_1.name = '' +item_sub_1.width = (50.0, 33.33333206176758, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0) +item_sub_1.fixed = (False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False) +item_sub_1.auto_update = True +item_sub_1.n_cols = 1 +item_sub_1.cols = 1 +item_sub_1.height = 1.0 +d.out_tablet_x = 0.03999999910593033 +d.out_frame = False +d.y = 0.20000000298023224 +d.in_tablet_z = 0.029999999329447746 +d.handle_altitude = 1.399999976158142 +d.out_frame_y2 = 0.019999999552965164 +d.out_tablet_y = 0.03999999910593033 +d.in_tablet_y = 0.03999999910593033 +d.out_frame_x = 0.10000000149011612 +d.offset = 0.10000000149011612 +d.window_shape = 'RECTANGLE' +d.frame_x = 0.05999999865889549 +d.x = 0.800000011920929 +d.z = 0.800000011920929 +d.hole_inside_mat = 1 +d.curve_steps = 16 +d.handle_enable = True +d.hole_outside_mat = 0 +d.out_tablet_z = 0.029999999329447746 +d.window_type = 'FLAT' +d.angle_y = 0.0 +d.elipsis_b = 0.5 +d.out_tablet_enable = True +d.out_frame_offset = 0.0 +d.warning = False +d.altitude = 1.2000000476837158 +d.blind_enable = False diff --git a/archipack/presets/archipack_window/80x80_flat_1_circle.png b/archipack/presets/archipack_window/80x80_flat_1_circle.png new file mode 100644 index 0000000000000000000000000000000000000000..bd856b377d67a52937a4d7625887e8f7f73d7d17 GIT binary patch literal 6914 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 literal 0 HcmV?d00001 diff --git a/archipack/presets/archipack_window/80x80_flat_1_circle.py b/archipack/presets/archipack_window/80x80_flat_1_circle.py new file mode 100644 index 000000000..18f5c8bc3 --- /dev/null +++ b/archipack/presets/archipack_window/80x80_flat_1_circle.py @@ -0,0 +1,58 @@ +import bpy +d = bpy.context.active_object.data.archipack_window[0] + +d.frame_y = 0.05999999865889549 +d.flip = False +d.blind_z = 0.029999999329447746 +d.blind_open = 80.0 +d.hole_margin = 0.10000000149011612 +d.out_frame_y = 0.019999999552965164 +d.blind_y = 0.0020000000949949026 +d.in_tablet_x = 0.03999999910593033 +d.in_tablet_enable = True +d.n_rows = 2 +d.radius = 0.9599999785423279 +d.rows.clear() +item_sub_1 = d.rows.add() +item_sub_1.name = '' +item_sub_1.width = (50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0) +item_sub_1.fixed = (False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False) +item_sub_1.auto_update = True +item_sub_1.n_cols = 2 +item_sub_1.cols = 2 +item_sub_1.height = 0.800000011920929 +item_sub_1 = d.rows.add() +item_sub_1.name = '' +item_sub_1.width = (50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0) +item_sub_1.fixed = (False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False) +item_sub_1.auto_update = True +item_sub_1.n_cols = 1 +item_sub_1.cols = 1 +item_sub_1.height = 1.0 +d.out_tablet_x = 0.03999999910593033 +d.out_frame = False +d.y = 0.800000011920929 +d.in_tablet_z = 0.029999999329447746 +d.handle_altitude = 1.399999976158142 +d.out_frame_y2 = 0.019999999552965164 +d.out_tablet_y = 0.03999999910593033 +d.in_tablet_y = 0.03999999910593033 +d.out_frame_x = 0.10000000149011612 +d.offset = 0.10000000149011612 +d.window_shape = 'CIRCLE' +d.frame_x = 0.05999999865889549 +d.x = 0.800000011920929 +d.z = 1.100000023841858 +d.hole_inside_mat = 1 +d.curve_steps = 32 +d.handle_enable = True +d.hole_outside_mat = 0 +d.out_tablet_z = 0.029999999329447746 +d.window_type = 'FLAT' +d.angle_y = 0.0 +d.elipsis_b = 0.5 +d.out_tablet_enable = True +d.out_frame_offset = 0.0 +d.warning = False +d.altitude = 1.0 +d.blind_enable = False diff --git a/archipack/presets/missing.png b/archipack/presets/missing.png new file mode 100644 index 0000000000000000000000000000000000000000..7881102a1884a6bfb7b78c94bdf25d142e2b69ec GIT binary patch literal 3874 zcmeAS@N?(olHy`uVBq!ia0y~yV3@|hz>vbh1|p+Reqdl=U`z6LcVU>vu#{ormj!n7 z85kHi3p^r=85sBzL6~uc{qjr(1_t&LPhVH|hin`Iye5}fra3S$@U8cBaSW-r^>%J$ zfk=4War^bV-8mW^g%bP<XPjP>dw?zGhG*!T8_#Z(&Rja_uFACT8`9w#@(M}USZ=p` z4VuuZE1xrGO>P!rGqcK!t<BSC$40b>8^j54C{H-xHb=;50e8t6pYNyTi~FnCJmuOX z&;Abc{d(rL`TF}GU*5Ogb-$YFz$Tr}@M)=!imWn=%oKIIckPP0qbJVS&U|_k!wdTY z1Jmnzi>3MUV%9rfH`(?5)Wc6pH)l9qSX^Fz=xLdyp-iTU*_FWh#eF?J)&*v!37(zn zq$8#+Ir(y@UsM!(3&UljjXJ`<!V11~^wjgFb8kQ0tH&ZFz+~e4plD~o&N&<`=6Ny4 z-<CZ#ka@AIkEO)dox{WbeMjDl9e34sC03k^7FvGUv;9kn|BYRJr=Pl9E@=@^P~EjK zIa0`%<J}wUj;>8XEEBHo<zM!aZNau(LhZ`DiY&91Eq=+iE3sl<wiUyyWtB!Q85|EX zE^2adb$f=~t^Ur?#`mUmi;m9aBPMq_RQ%F~XDz!J5Z|w=>vP$KqcJs7qK#RQPmCcb z$+L5c*tAqd-A(}}P1RZJ7G_HA%6m|@`$9|G5$1M&h57OGdE95|W^gnv%Zym|wAH18 zOGuzWMW8KpX2i7CXRKVB6E87On{w&DAd}AN&#{+n3ct2!G9`MRe%#0K{=e;g)=SlX zQS%P4-urydgj3N#Ci9>4(dTW~=cn$v$jd%0^AV@fms<_@_n&_HY`^_Yr|KyWy)z6r zI2zSh7A$;yBaN#e(c+KftKa+i7ftx^XAc{f$M=8Y4(2AePq2!&|M&Z@_`F@Kz*xcN z`JOF54u1H@npw`P_@>yce_r9fAMDPmEe@aWB^=@}NvRDjnDb5R@iIvnw${7(yZ^ft zcbBbA_@R`n_JQ>wzt#uUH{}N+k7!&zq#KcanIqu-1&@y}H@}r%Gg~u=?O2qq?hKcJ z0-0#(`+wQFGtXZN=-Rz1`;|rc#+b&XBGGSc11`L~yy8IDX03HGi$7&;W%AZ6n-nx@ z|NjE7C83R??sNB7%-r?*E0@mF8z(pEeEKfyH}i|uhDS?+@6S8@v`l-$lqB`-nVbHv zPS%{7#u_FQQt@ZXTV*}gFs3idCT&fa<0tOEU(a*r?2{gC9%_dl&-KVU&p#_Pv|@J2 z;(d*i^3Q2*n3A-*mc4&j&Mnc1?H-|5x5n%US$H6*Ywfos7ZO%5Y5Qrd-{BQnx)$QD zP0#(?gY4u*A`GwH{G58l+(c`G2-n8RsgX5>4?0e9u!iN7ehSI-vdsufIO>?U{p7*J z$3xvJu0C?NdKf%!k!<whl)4z<HBHG!HNP*g%-UJCaN@)SuOBQdz7n~dYbO3_J)e=M z`(;^XP++9%!Hr3J;W42JuFR{yZm3LGtWMWrzw>5K=K}}k%d?u(joVu}9Vff|t+U;{ zME5iolcQ2g+mx^g=7M747kTD5NEq34N3qVj{Pa=KPf<QuOUK;1ysTl_tB%ZeOp7+H z^1iwKnA7IWPmT(#)7Kx_yse3cIn=8&kwr}VqoWv4mu%3(x}{q-%-*HX!Xfx>dFiw@ z3l~n@EPl!|ree}EwdG8!b+dA-weQSNFYIfW_`!nxT9Ux558^xWzlvp=#s@6P4Xr$2 z^ebT#x3<N;wTwPqo_D`i?bF?R;NoM|mi31h9q8J)DDpstY)iw|%*fkX6H_85t(q0p z<v(@X?XS{#zYCmy{9->Se_zPOuA`~{V3_jFs%f*-UQD|>)o`)fl4AwUb02QnEU{hq zfJb+m%Hs`3=l6>&Ws7)t_kG6k*^UC7$8K|`E3MqOaAWNL*sLu}y1H6hJH^(;CR+$i znAmr6nLdB*-F5kMT4wlMoYZ<-DYW$@N9f(J(o^Q`zOwFM(0Q)>=AehX9IRn;LSuv7 zE4gew1tuJgto|;(#KlT9qVUSp)uADAtHTmBkNO3jnzAvBYmLRKJ&&|Zw_X>G@Y=XF z?5C#L`G7`I_PP5jET5*m)7a2dqAiyZJ?%-iesKP?zG*93f4^G2{@B_N`Ag5u**hah z|HseMCvUS&D0|yf(U`JbzyI*V2RAk*^T^xnnfbrs*!$g)+TEto+a8t8K9$t^kkw;J zYT~Rsoy(i+7reZ?{ao>v8lBTw-#+GhtJ@~B<o|zG%y0keNzu*|r%!Vic7L%A3@!SV z<K@IEDCfi;H}RmgqT{2ooqwu-a=w>qotC=rwG~sNrf$oHJ-07bPW%$3)E}yJ$fY&G zThzil^ODN8+}p>F^-9k)O6^*^lzZCcmrw4N-%q`}t8~hgifL&wW;qf&ckVnh%aptQ z@I|H9l6-5=uU)sUPtrK;!^`FKCrzAq@XpTSlc!HVzO^;`%pA+fXJ?x)er<Jmxqte@ zL#;EW9c6qk&2zk8Hu3qk`-Q7^aBRQr`=7CS#;?UO`^w+1nD9D0Mlbp9@za)pAsvS& z9=pLV|LpzzpXzZxPWCfKY)nh;_+K0UXX4)7<puBFNY3$7zj=3;aoQbco1cf}Cr-Sm zIdS!&hSI{_F^ZG9Kjc}g{2eFwZKgy2^0eukWkP1vdny@t-h2!X2{}^x{jR&8AD^qM z>yqWm+qbN5T72iql_M>j!Y9t3-|qWlalhTEZ*OnsbA_91SmrnPRQmi{u}AffkM-(A zZRu!c=WpAwK5A=MVDhozA1^!^>L%U(bL>LtT=Tkr-fVnQk65=)`&pbG6dWu1=zU}1 zW6M8R8wEBjnWFRit98xJ^LBe{e?9wfZ1(;o$!>0sLR%Z>I@+#hljdXJ_3%TU=jz4T zY<%s^zTf|t@h~4Yu-R|Q8+-X;scfwI9_zWU<hqx=yyvULEPsDzLa&5j)AabdnU^jF z-K%=Nb_MUZrQXvg&7bd|mZqj1wr0ViMNM5@T+{U9{if^1-q>4h?mb=aZ(i>Q<8+Iv zFB+z%rh9%o>SpknZN|I&^35}UoB8d(II|jjJJ|m7=ArT*9~SfK)O~lBldzV%Y;tqk z%m07sw*7gx{Ypsq#kIfZo|(D5BVPZ%{IfIBM{XM5H|aio&;H$$)3rZSQp+E=&Mf|M zm0kYx!Bb!N{Cg(EcF(lMTm1aym!JKGE+=hWrYn3pE^5Qo6{}Vyy|}Q@#KffF(GkvH z-{0qFhr0Djxh5tmYKO0Tax;BCFK7PGPfu<B|M}b_XFIE@smUyQ&EMbOjqCnYFq}Gd z>c!>d{x09wtjW3e@BCE3^}*56hu4TNn>h9E-0k<H`3}6cubud5Q}>xUw*43Rdp`fR zE&8GG|Kqns0^M5+{;jV0?aNvG{C`v8;r-%KbHk<>e~({QoPPeEPTlk7BU#CJwKAes zuUVrbrW0|%TYvA7H#ax`d?NgN_l4`rsy>}mpFOww)ym}z8hZcoq^s`N|Nr~%{obmt zU9aLoOM7>>^F4X8GDzoi%JFIEZ|=|MpMF~JQ3+d{Tvj#{{}S!>G2gaux3~LkkdR{I zJJ9-hDT7Yjho~bz;@0ljdpbnzPtl#r%l)7K{eD0Fa2xNNTU%dmJG$7poo}0Yw95DP zW4%urCzgLcvnBoJhPt`AGnU=lHZ?QqlA@V!aHFX2u63JBUIxk4|0zs6Gb8bJ`i%{V zB4T2jQcsI5^PMeLly`ZVFT>emhaWK~OYN@PFWYdl_?cnChr1~$DFN~E=g+)6-}2+_ zy~XNJQ^hqk?F55F=V{qp)VZ_o@2}vhXD0u*J<w(2m7CS`IHGgmak=Uq&ERDLAu0B; zuU;;n&z5j)P2`%my|Yf8^3u}Q{`q2Yf5p?O;ZA>!MDP1{E8fneP5ys@(z|7BVSk_P zpY>C^^nV1ywRLfBCr>^$F*OS+`eczEuAhDHbZwFm!~Uvj#h<&&11g^y`K1f%P4E7+ zsr${%t#e)NXHUM-`+bMI#-S6xUS3|l^ZC5$Ke<yrJZw{+SJAY$`unZ6pC^?2H`M+v zyL9Q&tveO`{r=u}i;IgRcbDaM%s)AK|M_ccqn)IlY&E@lre0Tn$z4{1sx!?pmL&qd zmw%i+-uST3boI24?Xt5MFH^6%;~l9Z%`0itvS-hpj<@sYT9<FC_?YC@C*xUFWhHA} zmh$ApM4iY@OI!o)YVG}gulmWeXOrg6^-W7tJ8%14=Joae>;Kod9WP<nRdV*p)}Q+m zp1jz&Wa-k2D}&Sjy;`sSVSD}amA9-*U&#pNy|e#kY+swpm@Hp2<II|zlGx)9Jr?`l zmr?VXcWCzieIb!+LZ(fb)SW8&=I3R_IqRPF{<ME{<IJqk(-L-%;{KK1TeQS;`ogYn z-@^a?{q^OE{J%6IVRgT>JMlJN`|Fr9v$CFK91Q<c_V;*0`UKOJmzL^;B{`-oXKZHs zd~CM20gwBoC9Zp4On11or_xwh-ET?x`+F8Le80Y(PQ1jw%;@>oR!`Qcn<6;ZUHUxz z#@_GeJte=FT|bs}_SL!0KWeWguJ_i^u$j5%YL!vsnr$w|mo7~$^EPwMetyJzn(EE! z1r7P$`YK;}^Hi999=@(?+r_$m)tx{4KCW$%$@KKHj>w;GT0C3rcHRDd?eNu;_A5`~ z5L>;JRr2Kh@~tVI3yvLAGt0YsId55~<XTpRu-5CBM5n&h@rjn-yk$$Ze17QlD<6Me zvz_61x@-BIqq`!NEpCS23bwU3y))s9*SC4`UcBZi&9}VI>uW`5U(5OP^I=)lbrr3U z`*SSszq?)(Af`R<FlXN6N0*ms8g_;@imIEMT58&bdaW`^6OAYg@XeK7`utRo)>*DK z(Q8hNh6g82O<@h2qoO)zcYs@|Z>-m{_{ODGbzC}EKL;nQoEP8ay?2(3^VQh(;Q^W( zlGeYwIWr|?$?BefgrgBm@1$<{KW%2x{&vZ<cQ@O&&;(79r1jQT<x19@yP3Q-zrDJ? zU47R&P=of)9igU$<xe#>Oi5bLCwu8dX}US<w4gUvH@ll(vR(pevBs(&dLXh!NHn5w z!ot@)Ctj{S5Y)BTX!kwG>(*8v6W0H;+<Vak)Ql}m%WgW@TqF|VwK3f7cu7^s>7WG7 zqj7tyzbfdSU3g&A)SjZ8v+81rTfSJ8WpdcxpH>iM&7_#%VZC1HHs7?4qMWnKbrW57 z*XgL{)E(#0Yd@^8Jl~%+%;_YbR;BQZH#&X>D=x9U%DCRqxM}mQOFM(zcC)<x&$#?^ X$)u&fb}wgOU|{fc^>bP0l+XkKSz(M% literal 0 HcmV?d00001 diff --git a/archipack/pyqtree.py b/archipack/pyqtree.py new file mode 100644 index 000000000..80b757275 --- /dev/null +++ b/archipack/pyqtree.py @@ -0,0 +1,187 @@ +# -*- coding:utf-8 -*- + +# <pep8 compliant> + +""" +# Pyqtree + +Pyqtree is a pure Python spatial index for GIS or rendering usage. +It stores and quickly retrieves items from a 2x2 rectangular grid area, +and grows in depth and detail as more items are added. +The actual quad tree implementation is adapted from +[Matt Rasmussen's compbio library](https://github.com/mdrasmus/compbio/blob/master/rasmus/quadtree.py) +and extended for geospatial use. + + +## Platforms + +Python 2 and 3. + + +## Dependencies + +Pyqtree is written in pure Python and has no dependencies. + + +## Installing It + +Installing Pyqtree can be done by opening your terminal or commandline and typing: + + pip install pyqtree + +Alternatively, you can simply download the "pyqtree.py" file and place +it anywhere Python can import it, such as the Python site-packages folder. + + +## Example Usage + +Start your script by importing the quad tree. + + from pyqtree import Index + +Setup the spatial index, giving it a bounding box area to keep track of. +The bounding box being in a four-tuple: (xmin, ymin, xmax, ymax). + + spindex = Index(bbox=(0, 0, 100, 100)) + +Populate the index with items that you want to be retrieved at a later point, +along with each item's geographic bbox. + + # this example assumes you have a list of items with bbox attribute + for item in items: + spindex.insert(item, item.bbox) + +Then when you have a region of interest and you wish to retrieve items from that region, +just use the index's intersect method. This quickly gives you a list of the stored items +whose bboxes intersects your region of interests. + + overlapbbox = (51, 51, 86, 86) + matches = spindex.intersect(overlapbbox) + +There are other things that can be done as well, but that's it for the main usage! + + +## More Information: + +- [Home Page](http://github.com/karimbahgat/Pyqtree) +- [API Documentation](http://pythonhosted.org/Pyqtree) + + +## License: + +This code is free to share, use, reuse, and modify according to the MIT license, see LICENSE.txt. + + +## Credits: + +- Karim Bahgat (2015) +- Joschua Gandert (2016) + +""" + + +__version__ = "0.25.0" + +# PYTHON VERSION CHECK +import sys + + +PYTHON3 = int(sys.version[0]) == 3 +if PYTHON3: + xrange = range + + +class _QuadNode(object): + def __init__(self, item, rect): + self.item = item + self.rect = rect + + +class _QuadTree(object): + """ + Internal backend version of the index. + The index being used behind the scenes. Has all the same methods as the user + index, but requires more technical arguments when initiating it than the + user-friendly version. + """ + def __init__(self, x, y, width, height, max_items, max_depth, _depth=0): + self.nodes = [] + self.children = [] + self.center = (x, y) + self.width, self.height = width, height + self.max_items = max_items + self.max_depth = max_depth + self._depth = _depth + + def _insert(self, item, bbox): + if len(self.children) == 0: + node = _QuadNode(item, bbox) + self.nodes.append(node) + if len(self.nodes) > self.max_items and self._depth < self.max_depth: + self._split() + else: + self._insert_into_children(item, bbox) + + def _intersect(self, rect, results=None): + if results is None: + results = set() + # search children + if self.children: + if rect[0] <= self.center[0]: + if rect[1] <= self.center[1]: + self.children[0]._intersect(rect, results) + if rect[3] >= self.center[1]: + self.children[1]._intersect(rect, results) + if rect[2] >= self.center[0]: + if rect[1] <= self.center[1]: + self.children[2]._intersect(rect, results) + if rect[3] >= self.center[1]: + self.children[3]._intersect(rect, results) + # search node at this level + for node in self.nodes: + if (node.rect[2] >= rect[0] and node.rect[0] <= rect[2] and + node.rect[3] >= rect[1] and node.rect[1] <= rect[3]): + results.add(node.item) + return results + + def _insert_into_children(self, item, rect): + # if rect spans center then insert here + if (rect[0] <= self.center[0] and rect[2] >= self.center[0] and + rect[1] <= self.center[1] and rect[3] >= self.center[1]): + node = _QuadNode(item, rect) + self.nodes.append(node) + else: + # try to insert into children + if rect[0] <= self.center[0]: + if rect[1] <= self.center[1]: + self.children[0]._insert(item, rect) + if rect[3] >= self.center[1]: + self.children[1]._insert(item, rect) + if rect[2] > self.center[0]: + if rect[1] <= self.center[1]: + self.children[2]._insert(item, rect) + if rect[3] >= self.center[1]: + self.children[3]._insert(item, rect) + + def _split(self): + quartwidth = self.width / 4.0 + quartheight = self.height / 4.0 + halfwidth = self.width / 2.0 + halfheight = self.height / 2.0 + x1 = self.center[0] - quartwidth + x2 = self.center[0] + quartwidth + y1 = self.center[1] - quartheight + y2 = self.center[1] + quartheight + new_depth = self._depth + 1 + self.children = [_QuadTree(x1, y1, halfwidth, halfheight, + self.max_items, self.max_depth, new_depth), + _QuadTree(x1, y2, halfwidth, halfheight, + self.max_items, self.max_depth, new_depth), + _QuadTree(x2, y1, halfwidth, halfheight, + self.max_items, self.max_depth, new_depth), + _QuadTree(x2, y2, halfwidth, halfheight, + self.max_items, self.max_depth, new_depth)] + nodes = self.nodes + self.nodes = [] + for node in nodes: + self._insert_into_children(node.item, node.rect) -- GitLab