Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Dinges <blender@dingto.org>2021-11-23 11:24:55 +0300
committerThomas Dinges <blender@dingto.org>2021-11-23 11:24:55 +0300
commitd7517a6f2a69071eab53c02a645f7651ccfffd45 (patch)
tree7a496b8ada3e764b7e6c438e30d8c2be49fb95cf
parent162cba016c8c11bcebea4d8d3cf80da9faf4ce76 (diff)
Remove Archipack to reflect new key requirements.
https://wiki.blender.org/wiki/Process/Addons
-rw-r--r--archipack/__init__.py526
-rw-r--r--archipack/archipack_2d.py968
-rw-r--r--archipack/archipack_autoboolean.py662
-rw-r--r--archipack/archipack_cutter.py929
-rw-r--r--archipack/archipack_door.py1899
-rw-r--r--archipack/archipack_fence.py1785
-rw-r--r--archipack/archipack_floor.py2102
-rw-r--r--archipack/archipack_gl.py1432
-rw-r--r--archipack/archipack_handle.py185
-rw-r--r--archipack/archipack_keymaps.py122
-rw-r--r--archipack/archipack_manipulator.py2415
-rw-r--r--archipack/archipack_material.py604
-rw-r--r--archipack/archipack_object.py284
-rw-r--r--archipack/archipack_preset.py580
-rw-r--r--archipack/archipack_progressbar.py90
-rw-r--r--archipack/archipack_reference_point.py480
-rw-r--r--archipack/archipack_rendering.py194
-rw-r--r--archipack/archipack_roof.py5413
-rw-r--r--archipack/archipack_slab.py1762
-rw-r--r--archipack/archipack_snap.py347
-rw-r--r--archipack/archipack_stair.py2845
-rw-r--r--archipack/archipack_thumbs.py235
-rw-r--r--archipack/archipack_truss.py380
-rw-r--r--archipack/archipack_wall2.py2437
-rw-r--r--archipack/archipack_window.py2267
-rw-r--r--archipack/bmesh_utils.py315
-rw-r--r--archipack/icons/archipack.pngbin1364 -> 0 bytes
-rw-r--r--archipack/icons/detect.pngbin281 -> 0 bytes
-rw-r--r--archipack/icons/door.pngbin414 -> 0 bytes
-rw-r--r--archipack/icons/fence.pngbin1779 -> 0 bytes
-rw-r--r--archipack/icons/floor.pngbin1457 -> 0 bytes
-rw-r--r--archipack/icons/polygons.pngbin242 -> 0 bytes
-rw-r--r--archipack/icons/roof.pngbin1483 -> 0 bytes
-rw-r--r--archipack/icons/selection.pngbin1021 -> 0 bytes
-rw-r--r--archipack/icons/slab.pngbin1620 -> 0 bytes
-rw-r--r--archipack/icons/stair.pngbin1486 -> 0 bytes
-rw-r--r--archipack/icons/truss.pngbin1462 -> 0 bytes
-rw-r--r--archipack/icons/union.pngbin1102 -> 0 bytes
-rw-r--r--archipack/icons/wall.pngbin637 -> 0 bytes
-rw-r--r--archipack/icons/window.pngbin579 -> 0 bytes
-rw-r--r--archipack/panel.py715
-rw-r--r--archipack/presets/archipack_door/160x200_dual.py23
-rw-r--r--archipack/presets/archipack_door/400x240_garage.py23
-rw-r--r--archipack/presets/archipack_door/80x200.py23
-rw-r--r--archipack/presets/archipack_fence/glass_panels.py67
-rw-r--r--archipack/presets/archipack_fence/inox_glass_concrete.py64
-rw-r--r--archipack/presets/archipack_fence/metal.py67
-rw-r--r--archipack/presets/archipack_fence/metal_glass.py67
-rw-r--r--archipack/presets/archipack_fence/wood.py67
-rw-r--r--archipack/presets/archipack_floor/boards_200x20.py31
-rw-r--r--archipack/presets/archipack_floor/herringbone_50x10.py31
-rw-r--r--archipack/presets/archipack_floor/herringbone_p_50x10.py31
-rw-r--r--archipack/presets/archipack_floor/hexagon_10.py31
-rw-r--r--archipack/presets/archipack_floor/hopscotch_30x30.py31
-rw-r--r--archipack/presets/archipack_floor/parquet_15x3.py31
-rw-r--r--archipack/presets/archipack_floor/stepping_stone_30x30.py31
-rw-r--r--archipack/presets/archipack_floor/tile_30x60.py31
-rw-r--r--archipack/presets/archipack_floor/windmill_30x30.py31
-rw-r--r--archipack/presets/archipack_materials/door.txt4
-rw-r--r--archipack/presets/archipack_materials/fence.txt4
-rw-r--r--archipack/presets/archipack_materials/floor.txt22
-rw-r--r--archipack/presets/archipack_materials/handle.txt2
-rw-r--r--archipack/presets/archipack_materials/roof.txt48
-rw-r--r--archipack/presets/archipack_materials/slab.txt3
-rw-r--r--archipack/presets/archipack_materials/stair.txt6
-rw-r--r--archipack/presets/archipack_materials/truss.txt1
-rw-r--r--archipack/presets/archipack_materials/wall2.txt8
-rw-r--r--archipack/presets/archipack_materials/window.txt6
-rw-r--r--archipack/presets/archipack_roof/braas_1.py31
-rw-r--r--archipack/presets/archipack_roof/braas_2.py31
-rw-r--r--archipack/presets/archipack_roof/eternit.py31
-rw-r--r--archipack/presets/archipack_roof/lauze.py31
-rw-r--r--archipack/presets/archipack_roof/metal.py31
-rw-r--r--archipack/presets/archipack_roof/ondule.py30
-rw-r--r--archipack/presets/archipack_roof/roman.py30
-rw-r--r--archipack/presets/archipack_roof/round.py31
-rw-r--r--archipack/presets/archipack_roof/square.py31
-rw-r--r--archipack/presets/archipack_stair/i_wood_over_concrete.py117
-rw-r--r--archipack/presets/archipack_stair/l_wood_over_concrete.py155
-rw-r--r--archipack/presets/archipack_stair/o_wood_over_concrete.py136
-rw-r--r--archipack/presets/archipack_stair/u_wood_over_concrete.py155
-rw-r--r--archipack/presets/archipack_window/120x110_flat_2.py50
-rw-r--r--archipack/presets/archipack_window/120x110_flat_2_elliptic.py58
-rw-r--r--archipack/presets/archipack_window/120x110_flat_2_oblique.py50
-rw-r--r--archipack/presets/archipack_window/120x110_flat_2_round.py58
-rw-r--r--archipack/presets/archipack_window/180x110_flat_3.py50
-rw-r--r--archipack/presets/archipack_window/180x210_flat_3.py50
-rw-r--r--archipack/presets/archipack_window/180x210_rail_2.py50
-rw-r--r--archipack/presets/archipack_window/240x210_rail_3.py50
-rw-r--r--archipack/presets/archipack_window/80x80_flat_1.py50
-rw-r--r--archipack/presets/archipack_window/80x80_flat_1_circle.py58
-rw-r--r--archipack/presets/missing.pngbin1261 -> 0 bytes
92 files changed, 0 insertions, 34121 deletions
diff --git a/archipack/__init__.py b/archipack/__init__.py
deleted file mode 100644
index 44c56b7c..00000000
--- a/archipack/__init__.py
+++ /dev/null
@@ -1,526 +0,0 @@
-# -*- 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',
- 'author': 's-leger',
- 'license': 'GPL',
- 'deps': '',
- 'version': (1, 2, 85),
- 'blender': (3, 0, 0),
- 'location': 'View3D > Sidebar > Create > Archipack',
- 'warning': '',
- 'doc_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_material)
- 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_wall2)
- imp.reload(archipack_roof)
- imp.reload(archipack_slab)
- imp.reload(archipack_fence)
- imp.reload(archipack_truss)
- imp.reload(archipack_floor)
- imp.reload(archipack_rendering)
-
- # print("archipack: reload ready")
-else:
- from . import archipack_material
- 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_wall2
- from . import archipack_roof
- from . import archipack_slab
- from . import archipack_fence
- from . import archipack_truss
- from . import archipack_floor
- from . import archipack_rendering
- # 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
-# ----------------------------------------------------
-
-
-class Archipack_Pref(AddonPreferences):
- bl_idname = __name__
-
- 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 & drag 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
- )
- constant_handle_size: BoolProperty(
- name="Constant handle size",
- description="When checked, handle size on scree remains constant (handle size pixels)",
- default=False
- )
- text_size: IntProperty(
- name="Manipulators",
- description="Manipulator font size (pixels)",
- min=2,
- default=14
- )
- handle_colour_selected: FloatVectorProperty(
- name="Selected handle colour",
- description="Handle color when selected",
- subtype='COLOR_GAMMA',
- default=(0.0, 0.0, 0.7, 1.0),
- size=4,
- min=0, max=1
- )
- handle_colour_inactive: FloatVectorProperty(
- name="Inactive handle colour",
- description="Handle color when disabled",
- subtype='COLOR_GAMMA',
- default=(0.3, 0.3, 0.3, 1.0),
- size=4,
- min=0, max=1
- )
- handle_colour_normal: FloatVectorProperty(
- name="Handle colour normal",
- description="Base handle color when not selected",
- subtype='COLOR_GAMMA',
- default=(1.0, 1.0, 1.0, 1.0),
- size=4,
- min=0, max=1
- )
- handle_colour_hover: FloatVectorProperty(
- name="Handle colour hover",
- description="Handle color when mouse hover",
- subtype='COLOR_GAMMA',
- default=(1.0, 1.0, 0.0, 1.0),
- size=4,
- min=0, max=1
- )
- handle_colour_active: FloatVectorProperty(
- name="Handle colour active",
- description="Handle colour when moving",
- subtype='COLOR_GAMMA',
- default=(1.0, 0.0, 0.0, 1.0),
- size=4,
- min=0, max=1
- )
- matlib_path : StringProperty(
- name="Folder path",
- description="absolute path to material library folder",
- default="",
- subtype="DIR_PATH"
- )
- # 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="Setup actions")
- col.prop(self, "matlib_path", text="Material library")
- box.label(text="Render presets Thumbnails")
- box.operator("archipack.render_thumbs", icon='ERROR')
- box = layout.box()
- row = box.row()
- col = row.column()
- col.label(text="Add menu:")
- col.prop(self, "create_submenu")
-
- box = layout.box()
- box.label(text="Features")
- box.prop(self, "max_style_draw_tool")
- box = layout.box()
- row = box.row()
- split = row.split(factor=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")
- row = col.row(align=True)
- row.prop(self, "handle_colour_normal")
- row = col.row(align=True)
- row.prop(self, "handle_colour_hover")
- row = col.row(align=True)
- row.prop(self, "handle_colour_active")
- row = col.row(align=True)
- row.prop(self, "handle_colour_selected")
- row = col.row(align=True)
- row.prop(self, "handle_colour_inactive")
- 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")
- col.prop(self, "text_size")
- col.prop(self, "constant_handle_size")
-
-# ----------------------------------------------------
-# Archipack panel
-# ----------------------------------------------------
-
-class TOOLS_PT_Archipack_Create(Panel):
- bl_label = "Archipack"
- bl_idname = "TOOLS_PT_Archipack_Create"
- bl_space_type = "VIEW_3D"
- bl_region_type = "UI"
- bl_category = "Create"
- bl_context = "objectmode"
- bl_options = {'DEFAULT_CLOSED'}
-
- @classmethod
- def poll(self, context):
- return True
-
- def draw(self, context):
- global icons_collection
-
- icons = icons_collection["main"]
- layout = self.layout
- box = layout.box()
- box.operator("archipack.auto_boolean", text="Boolean", icon='AUTO').mode = 'HYBRID'
- row = layout.row(align=True)
- box = row.box()
- box.label(text="Create")
- 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.roof_preset_menu",
- text="Roof",
- icon_value=icons["roof"].icon_id
- ).preset_operator = "archipack.roof"
- row = box.row(align=True)
- row.operator("archipack.floor_preset_menu",
- text="Floor",
- icon_value=icons["floor"].icon_id
- ).preset_operator = "archipack.floor"
- row.operator("archipack.floor_preset_menu",
- text="->Wall",
- icon_value=icons["floor"].icon_id
- ).preset_operator = "archipack.floor_from_wall"
- row.operator("archipack.floor_preset_menu",
- text="",
- icon='CURVE_DATA').preset_operator = "archipack.floor_from_curve"
-
-
-# ----------------------------------------------------
-# 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
- ).preset_operator = "archipack.floor"
- layout.operator("archipack.roof_preset_menu",
- text="Roof",
- icon_value=icons["roof"].icon_id
- ).preset_operator = "archipack.roof"
-
-
-class ARCHIPACK_MT_create(Menu):
- bl_label = 'Archipack'
-
- 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.preferences.addons[__name__].preferences.create_submenu:
- layout.operator_context = 'INVOKE_REGION_WIN'
- layout.menu("ARCHIPACK_MT_create", icon_value=icons["archipack"].icon_id)
- else:
- draw_menu(self, context)
-
-
-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_material.register()
- archipack_snap.register()
- archipack_manipulator.register()
- archipack_reference_point.register()
- archipack_autoboolean.register()
- archipack_door.register()
- archipack_window.register()
- archipack_stair.register()
- archipack_wall2.register()
- archipack_roof.register()
- archipack_slab.register()
- archipack_fence.register()
- archipack_truss.register()
- archipack_floor.register()
- archipack_rendering.register()
- bpy.utils.register_class(Archipack_Pref)
- bpy.utils.register_class(TOOLS_PT_Archipack_Create)
- bpy.utils.register_class(ARCHIPACK_MT_create)
- bpy.types.VIEW3D_MT_mesh_add.append(menu_func)
-
-
-def unregister():
- global icons_collection
- bpy.types.VIEW3D_MT_mesh_add.remove(menu_func)
- bpy.utils.unregister_class(ARCHIPACK_MT_create)
- bpy.utils.unregister_class(TOOLS_PT_Archipack_Create)
- bpy.utils.unregister_class(Archipack_Pref)
- archipack_material.unregister()
- archipack_snap.unregister()
- archipack_manipulator.unregister()
- archipack_reference_point.unregister()
- archipack_autoboolean.unregister()
- archipack_door.unregister()
- archipack_window.unregister()
- archipack_stair.unregister()
- archipack_wall2.unregister()
- archipack_roof.unregister()
- archipack_slab.unregister()
- archipack_fence.unregister()
- archipack_truss.unregister()
- archipack_floor.unregister()
- archipack_rendering.unregister()
-
- 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
deleted file mode 100644
index e286e730..00000000
--- a/archipack/archipack_2d.py
+++ /dev/null
@@ -1,968 +0,0 @@
-# -*- 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))
- self.line = None
-
- @property
- def copy(self):
- return Line(self.p.copy(), self.v.copy())
-
- @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 a0(self):
- return self.angle
-
- @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.dot(c)
- if d == 0:
- return False, 0, 0
- t = c.dot(line.p - self.p) / d
- return True, self.lerp(t), t
-
- def intersect_ext(self, line):
- """
- same as intersect, but return param t on both lines
- """
- c = line.cross_z
- d = self.v.dot(c)
- if d == 0:
- return False, 0, 0, 0
- dp = line.p - self.p
- c2 = self.cross_z
- u = c.dot(dp) / d
- v = c2.dot(dp) / d
- return u > 0 and v > 0 and u < 1 and v < 1, self.lerp(u), u, v
-
- 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
- if dl == 0:
- return dp.length < 0.00001, 0, 0
- d = (self.v.x * dp.y - self.v.y * dp.x) / dl
- t = self.v.dot(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
- """
- curve = bpy.data.curves.new('LINE', 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, z = p
- spline.points[i].co = (x, y, 0, 1)
- curve_obj = bpy.data.objects.new('LINE', curve)
- context.scene.collection.objects.link(curve_obj)
- curve_obj.select_set(state=True)
-
- 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.dot(c)
- if d == 0:
- return line
- v = line.p - last.p
- t = c.dot(v) / d
- c2 = last.cross_z
- u = c2.dot(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.dot(line.v)
- B = 2 * v.dot(line.v)
- C = v.dot(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:
- 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.line = None
- 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)
-
- @property
- def oposite(self):
- a0 = self.a0 + self.da
- if a0 > pi:
- a0 -= 2 * pi
- if a0 < -pi:
- a0 += 2 * pi
- return Arc(self.c, self.r, a0, -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)
-
- def intersect_ext(self, line):
- """
- same as intersect, but return param t on both lines
- """
- res, p, v = self.intersect(line)
- v0 = self.p0 - self.c
- v1 = p - self.c
- u = self.signed_angle(v0, v1) / self.da
- return res and u > 0 and v > 0 and u < 1 and v < 1, p, u, v
-
- # 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 as_lines(self, steps):
- """
- convert Arc to lines
- """
- res = []
- p0 = self.lerp(0)
- for step in range(steps):
- p1 = self.lerp((step + 1) / steps)
- s = Line(p0=p0, p1=p1)
- res.append(s)
- p0 = p1
-
- if self.line is not None:
- p0 = self.line.lerp(0)
- for step in range(steps):
- p1 = self.line.lerp((step + 1) / steps)
- res[step].line = Line(p0=p0, p1=p1)
- p0 = p1
- return res
-
- 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):
- """
- Tangent 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 Tangent 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 tangent 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 around 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.collection.objects.link(curve_obj)
- curve_obj.select_set(state=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
- positioned 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
deleted file mode 100644
index 3a424728..00000000
--- a/archipack/archipack_autoboolean.py
+++ /dev/null
@@ -1,662 +0,0 @@
-# -*- 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 . archipack_object import ArchipackCollectionManager
-
-class ArchipackBoolManager(ArchipackCollectionManager):
- """
- 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):
- """
- mode in 'ROBUST', 'INTERACTIVE', 'HYBRID'
- """
- self.mode = 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 is None:
- return
- 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.display_type = 'WIRE'
- hole.hide_render = True
- hole.hide_select = True
- hole.select_set(state=True)
-
- 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'
- m.object = hole
-
- def union(self, basis, hole):
- # print("union %s" % (hole.name))
- m = basis.modifiers.new('AutoMerge', 'BOOLEAN')
- m.operation = 'UNION'
- 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:
- self.unlink_object_from_scene(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)
- self.link_object_to_scene(context, 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()
- for mat in wall.data.materials:
- hole_obj.data.materials.append(mat)
- # 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 tagged with "archipack_robusthole"
- interactive -> mixed:
- many modifisers on wall tagged 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.operation = 'DIFFERENCE'
-
- if m.object is None:
- hole_obj = self.create_merge_basis(context, wall)
- else:
- hole_obj = m.object
-
- m.object = hole_obj
- self.prepare_hole(hole_obj)
-
- 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)
-
- # add modifier and holes not found in existing
- for h in holes:
- if h not in existing:
- self.union(hole_obj, h)
-
- # 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)
- self.unlink_object_from_scene(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
- """
-
- if wall.data is not None and "archipack_wall2" in wall.data:
- # ensure wall modifier is there before any boolean
- # to support "revival" of applied modifiers
- m = wall.modifiers.get("Wall")
- if m is None:
- wall.select_set(state=True)
- context.view_layer.objects.active = wall
- wall.data.archipack_wall2[0].update(context)
-
- bpy.ops.object.select_all(action='DESELECT')
- context.view_layer.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)
-
- self.sort_holes(wall, holes)
-
- # hole(s) are selected and active after this one
- for hole in holes:
- # copy wall material to hole
- hole.data.materials.clear()
- for mat in wall.data.materials:
- hole.data.materials.append(mat)
-
- self.prepare_hole(hole)
-
- # 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.view_layer.objects.active = wall
- bpy.ops.archipack.reference_point()
- else:
- wall.parent.select_set(state=True)
- context.view_layer.objects.active = wall.parent
-
- wall.select_set(state=True)
- for o in childs:
- if 'archipack_robusthole' in o:
- o.hide_select = False
- o.select_set(state=True)
-
- bpy.ops.archipack.parent_to_reference()
-
- for o in childs:
- if 'archipack_robusthole' in o:
- o.hide_select = True
-
- 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
-
- hole.data.materials.clear()
- for mat in wall.data.materials:
- hole.data.materials.append(mat)
-
- 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'
-
- 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.view_layer.objects.active = wall
- bpy.ops.archipack.reference_point()
- else:
- context.view_layer.objects.active = wall.parent
-
- if hole_obj is not None:
- hole_obj.select_set(state=True)
-
- wall.select_set(state=True)
- o.select_set(state=True)
- bpy.ops.archipack.parent_to_reference()
- wall.select_set(state=True)
- context.view_layer.objects.active = wall
- if "archipack_wall2" in wall.data:
- d = wall.data.archipack_wall2[0]
- g = d.get_generator()
- d.setup_childs(wall, g)
- d.relocate_childs(context, wall, g)
- elif "archipack_roof" in wall.data:
- pass
- 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'
- )
- """
- Wall must be active object
- window or door must be selected
- """
-
- @classmethod
- def poll(cls, context):
- w = context.active_object
- return (w is not None and w.data is not None and
- ("archipack_wall2" in w.data or
- "archipack_wall" in w.data or
- "archipack_roof" 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)
- for o in context.selected_objects:
- if o != wall:
- manager.singleboolean(context, wall, o)
- o.select_set(state=False)
- break
- wall.select_set(state=True)
- context.view_layer.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'
- )
-
- def draw(self, context):
- layout = self.layout
- row = layout.row()
- row.prop(self, 'mode')
-
- def execute(self, context):
- if context.mode == "OBJECT":
- manager = ArchipackBoolManager(mode=self.mode)
- active = context.view_layer.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_set(state=True)
- context.view_layer.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_set(state=True)
- context.view_layer.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, a window or a roof")
- return {'CANCELLED'}
- bpy.ops.object.select_all(action='DESELECT')
- o.select_set(state=True)
- context.view_layer.objects.active = o
- hole = manager._generate_hole(context, o)
- manager.prepare_hole(hole)
- hole.select_set(state=False)
- o.select_set(state=True)
- context.view_layer.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_cutter.py b/archipack/archipack_cutter.py
deleted file mode 100644
index ec43ad19..00000000
--- a/archipack/archipack_cutter.py
+++ /dev/null
@@ -1,929 +0,0 @@
-# -*- 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)
-# Cutter / CutAble shared by roof, slab, and floor
-# ----------------------------------------------------------
-from mathutils import Vector, Matrix
-from mathutils.geometry import interpolate_bezier
-from math import cos, sin, pi, atan2
-import bmesh
-from random import uniform
-from bpy.props import (
- FloatProperty, IntProperty, BoolProperty,
- StringProperty, EnumProperty
- )
-from .archipack_2d import Line
-
-
-class CutterSegment(Line):
-
- def __init__(self, p, v, type='DEFAULT'):
- Line.__init__(self, p, v)
- self.type = type
- self.is_hole = True
-
- @property
- def copy(self):
- return CutterSegment(self.p.copy(), self.v.copy(), self.type)
-
- def straight(self, length, t=1):
- s = self.copy
- s.p = self.lerp(t)
- s.v = self.v.normalized() * length
- return s
-
- def set_offset(self, offset, last=None):
- """
- Offset line and compute intersection point
- between segments
- """
- self.line = self.make_offset(offset, last)
-
- def offset(self, offset):
- s = self.copy
- s.p += self.sized_normal(0, offset).v
- return s
-
- @property
- def oposite(self):
- s = self.copy
- s.p += s.v
- s.v = -s.v
- return s
-
-
-class CutterGenerator():
-
- def __init__(self, d):
- self.parts = d.parts
- self.operation = d.operation
- self.segs = []
-
- def add_part(self, part):
-
- if len(self.segs) < 1:
- s = None
- else:
- s = self.segs[-1]
-
- # start a new Cutter
- if s is None:
- v = part.length * Vector((cos(part.a0), sin(part.a0)))
- s = CutterSegment(Vector((0, 0)), v, part.type)
- else:
- s = s.straight(part.length).rotate(part.a0)
- s.type = part.type
-
- self.segs.append(s)
-
- 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):
- # Make last segment implicit closing one
- s0 = self.segs[-1]
- s1 = self.segs[0]
- dp = s1.p0 - s0.p0
- s0.v = dp
-
- if len(self.segs) > 1:
- s0.line = s0.make_offset(self.parts[-1].offset, self.segs[-2].line)
-
- p1 = s1.line.p1
- s1.line = s1.make_offset(self.parts[0].offset, s0.line)
- s1.line.p1 = p1
-
- def locate_manipulators(self):
- if self.operation == 'DIFFERENCE':
- side = -1
- else:
- side = 1
- 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:
-
- 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 = f.straight(side, 0).v.to_3d()
- manipulators[0].set_pts([p0, v0, v1])
-
- # segment length
- manipulators[1].type_key = 'SIZE'
- manipulators[1].prop1_name = "length"
- manipulators[1].set_pts([p0, p1, (side, 0, 0)])
-
- # snap manipulator, don't change index !
- manipulators[2].set_pts([p0, p1, (side, 0, 0)])
- # dumb segment id
- manipulators[3].set_pts([p0, p1, (side, 0, 0)])
-
- # offset
- manipulators[4].set_pts([
- p0,
- p0 + f.sized_normal(0, max(0.0001, self.parts[i].offset)).v.to_3d(),
- (0.5, 0, 0)
- ])
-
- def change_coordsys(self, fromTM, toTM):
- """
- move shape fromTM into toTM coordsys
- """
- dp = (toTM.inverted() @ fromTM.translation).to_2d()
- da = toTM.row[1].to_2d().angle_signed(fromTM.row[1].to_2d())
- ca = cos(da)
- sa = sin(da)
- rM = Matrix([
- [ca, -sa],
- [sa, ca]
- ])
- for s in self.segs:
- tp = (rM @ s.p0) - s.p0 + dp
- s.rotate(da)
- s.translate(tp)
-
- def get_index(self, index):
- n_segs = len(self.segs)
- if index >= n_segs:
- index -= n_segs
- return index
-
- def next_seg(self, index):
- idx = self.get_index(index + 1)
- return self.segs[idx]
-
- def last_seg(self, index):
- return self.segs[index - 1]
-
- def get_verts(self, verts, edges):
-
- n_segs = len(self.segs) - 1
-
- for s in self.segs:
- verts.append(s.line.p0.to_3d())
-
- for i in range(n_segs):
- edges.append([i, i + 1])
-
-
-class CutAblePolygon():
- """
- Simple boolean operations
- Cutable generator / polygon
- Object MUST have properties
- - segs
- - holes
- - convex
- """
- def as_lines(self, step_angle=0.104):
- """
- Convert curved segments to straight lines
- """
- segs = []
- for s in self.segs:
- if "Curved" in type(s).__name__:
- dt, steps = s.steps_by_angle(step_angle)
- segs.extend(s.as_lines(steps))
- else:
- segs.append(s)
- self.segs = segs
-
- def inside(self, pt, segs=None):
- """
- Point inside poly (raycast method)
- support concave polygons
- TODO:
- make s1 angle different than all othr segs
- """
- s1 = Line(pt, Vector((min(10000, 100 * self.xsize), uniform(-0.5, 0.5))))
- counter = 0
- if segs is None:
- segs = self.segs
- for s in segs:
- res, p, t, u = s.intersect_ext(s1)
- if res:
- counter += 1
- return counter % 2 == 1
-
- def get_index(self, index):
- n_segs = len(self.segs)
- if index >= n_segs:
- index -= n_segs
- return index
-
- def is_convex(self):
- n_segs = len(self.segs)
- self.convex = True
- sign = False
- s0 = self.segs[-1]
- for i in range(n_segs):
- s1 = self.segs[i]
- if "Curved" in type(s1).__name__:
- self.convex = False
- return
- c = s0.v.cross(s1.v)
- if i == 0:
- sign = (c > 0)
- elif sign != (c > 0):
- self.convex = False
- return
- s0 = s1
-
- def get_intersections(self, border, cutter, s_start, segs, start_by_hole):
- """
- Detect all intersections
- for boundary: store intersection point, t, idx of segment, idx of cutter
- sort by t
- """
- s_segs = border.segs
- b_segs = cutter.segs
- s_nsegs = len(s_segs)
- b_nsegs = len(b_segs)
- inter = []
-
- # find all intersections
- for idx in range(s_nsegs):
- s_idx = border.get_index(s_start + idx)
- s = s_segs[s_idx]
- for b_idx, b in enumerate(b_segs):
- res, p, u, v = s.intersect_ext(b)
- if res:
- inter.append((s_idx, u, b_idx, v, p))
-
- # print("%s" % (self.side))
- # print("%s" % (inter))
-
- if len(inter) < 1:
- return True
-
- # sort by seg and param t of seg
- inter.sort()
-
- # reorder so we really start from s_start
- for i, it in enumerate(inter):
- if it[0] >= s_start:
- order = i
- break
-
- inter = inter[order:] + inter[:order]
-
- # print("%s" % (inter))
- p0 = border.segs[s_start].p0
-
- n_inter = len(inter) - 1
-
- for i in range(n_inter):
- s_end, u, b_start, v, p = inter[i]
- s_idx = border.get_index(s_start)
- s = s_segs[s_idx].copy
- s.is_hole = not start_by_hole
- segs.append(s)
- idx = s_idx
- max_iter = s_nsegs
- # walk through s_segs until intersection
- while s_idx != s_end and max_iter > 0:
- idx += 1
- s_idx = border.get_index(idx)
- s = s_segs[s_idx].copy
- s.is_hole = not start_by_hole
- segs.append(s)
- max_iter -= 1
- segs[-1].p1 = p
-
- s_start, u, b_end, v, p = inter[i + 1]
- b_idx = cutter.get_index(b_start)
- s = b_segs[b_idx].copy
- s.is_hole = start_by_hole
- segs.append(s)
- idx = b_idx
- max_iter = b_nsegs
- # walk through b_segs until intersection
- while b_idx != b_end and max_iter > 0:
- idx += 1
- b_idx = cutter.get_index(idx)
- s = b_segs[b_idx].copy
- s.is_hole = start_by_hole
- segs.append(s)
- max_iter -= 1
- segs[-1].p1 = p
-
- # add part between last intersection and start point
- idx = s_start
- s_idx = border.get_index(s_start)
- s = s_segs[s_idx].copy
- s.is_hole = not start_by_hole
- segs.append(s)
- max_iter = s_nsegs
- # go until end of segment is near start of first one
- while (s_segs[s_idx].p1 - p0).length > 0.0001 and max_iter > 0:
- idx += 1
- s_idx = border.get_index(idx)
- s = s_segs[s_idx].copy
- s.is_hole = not start_by_hole
- segs.append(s)
- max_iter -= 1
-
- if len(segs) > s_nsegs + b_nsegs + 1:
- # print("slice failed found:%s of:%s" % (len(segs), s_nsegs + b_nsegs))
- return False
-
- for i, s in enumerate(segs):
- s.p0 = segs[i - 1].p1
-
- return True
-
- def slice(self, cutter):
- """
- Simple 2d Boolean between boundary and roof part
- Doesn't handle slicing roof into multiple parts
-
- 4 cases:
- 1 pitch has point in boundary -> start from this point
- 2 boundary has point in pitch -> start from this point
- 3 no points inside -> find first crossing segment
- 4 not points inside and no crossing segments
- """
- # print("************")
-
- # keep inside or cut inside
- # keep inside must be CCW
- # cut inside must be CW
- keep_inside = (cutter.operation == 'INTERSECTION')
-
- start = -1
-
- f_segs = self.segs
- c_segs = cutter.segs
- store = []
-
- slice_res = True
- is_inside = False
-
- # find if either a cutter or
- # cutter intersects
- # (at least one point of any must be inside other one)
-
- # find a point of this pitch inside cutter
- for i, s in enumerate(f_segs):
- res = self.inside(s.p0, c_segs)
- if res:
- is_inside = True
- if res == keep_inside:
- start = i
- # print("pitch pt %sside f_start:%s %s" % (in_out, start, self.side))
- slice_res = self.get_intersections(self, cutter, start, store, True)
- break
-
- # seek for point of cutter inside pitch
- for i, s in enumerate(c_segs):
- res = self.inside(s.p0)
- if res:
- is_inside = True
- # no pitch point found inside cutter
- if start < 0 and res == keep_inside:
- start = i
- # print("cutter pt %sside c_start:%s %s" % (in_out, start, self.side))
- # swap cutter / pitch so we start from cutter
- slice_res = self.get_intersections(cutter, self, start, store, False)
- break
-
- # no points found at all
- if start < 0:
- # print("no pt inside")
- return not keep_inside
-
- if not slice_res:
- # print("slice fails")
- # found more segments than input
- # cutter made more than one loop
- return True
-
- if len(store) < 1:
- if is_inside:
- # print("not touching, add as hole")
- if keep_inside:
- self.segs = cutter.segs
- else:
- self.holes.append(cutter)
-
- return True
-
- self.segs = store
- self.is_convex()
-
- return True
-
-
-class CutAbleGenerator():
-
- def bissect(self, bm,
- plane_co,
- plane_no,
- dist=0.001,
- use_snap_center=False,
- clear_outer=True,
- clear_inner=False
- ):
- 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
- )
-
- def cut_holes(self, bm, cutable, offset={'DEFAULT': 0}):
- o_keys = offset.keys()
- has_offset = len(o_keys) > 1 or offset['DEFAULT'] != 0
- # cut holes
- for hole in cutable.holes:
-
- if has_offset:
-
- for s in hole.segs:
- if s.length > 0:
- if s.type in o_keys:
- of = offset[s.type]
- else:
- of = offset['DEFAULT']
- n = s.sized_normal(0, 1).v
- p0 = s.p0 + n * of
- self.bissect(bm, p0.to_3d(), n.to_3d(), clear_outer=False)
-
- # compute boundary with offset
- new_s = None
- segs = []
- for s in hole.segs:
- if s.length > 0:
- if s.type in o_keys:
- of = offset[s.type]
- else:
- of = offset['DEFAULT']
- new_s = s.make_offset(of, new_s)
- segs.append(new_s)
- # last / first intersection
- if len(segs) > 0:
- res, p0, t = segs[0].intersect(segs[-1])
- if res:
- segs[0].p0 = p0
- segs[-1].p1 = p0
-
- else:
- for s in hole.segs:
- if s.length > 0:
- n = s.sized_normal(0, 1).v
- self.bissect(bm, s.p0.to_3d(), n.to_3d(), clear_outer=False)
- # use hole boundary
- segs = hole.segs
- if len(segs) > 0:
- # when hole segs are found clear parts inside hole
- f_geom = [f for f in bm.faces
- if cutable.inside(
- f.calc_center_median().to_2d(),
- segs=segs)]
- if len(f_geom) > 0:
- bmesh.ops.delete(bm, geom=f_geom, context='FACES')
-
- def cut_boundary(self, bm, cutable, offset={'DEFAULT': 0}):
- o_keys = offset.keys()
- has_offset = len(o_keys) > 1 or offset['DEFAULT'] != 0
- # cut outside parts
- if has_offset:
- for s in cutable.segs:
- if s.length > 0:
- if s.type in o_keys:
- of = offset[s.type]
- else:
- of = offset['DEFAULT']
- n = s.sized_normal(0, 1).v
- p0 = s.p0 + n * of
- self.bissect(bm, p0.to_3d(), n.to_3d(), clear_outer=cutable.convex)
- else:
- for s in cutable.segs:
- if s.length > 0:
- n = s.sized_normal(0, 1).v
- self.bissect(bm, s.p0.to_3d(), n.to_3d(), clear_outer=cutable.convex)
-
- if not cutable.convex:
- f_geom = [f for f in bm.faces
- if not cutable.inside(f.calc_center_median().to_2d())]
- if len(f_geom) > 0:
- bmesh.ops.delete(bm, geom=f_geom, context='FACES')
-
-
-def update_hole(self, context):
- # update parent's only when manipulated
- self.update(context, update_parent=True)
-
-
-class ArchipackCutterPart():
- """
- Cutter segment PropertyGroup
-
- Childs MUST implements
- -find_in_selection
- Childs MUST define
- -type EnumProperty
- """
- length : FloatProperty(
- name="Length",
- min=0.01,
- max=1000.0,
- default=2.0,
- update=update_hole
- )
- a0 : FloatProperty(
- name="Angle",
- min=-2 * pi,
- max=2 * pi,
- default=0,
- subtype='ANGLE', unit='ROTATION',
- update=update_hole
- )
- offset : FloatProperty(
- name="Offset",
- min=0,
- default=0,
- update=update_hole
- )
-
- def find_in_selection(self, context):
- raise NotImplementedError
-
- def draw(self, layout, context, index):
- box = layout.box()
- box.prop(self, "type", text=str(index + 1))
- box.prop(self, "length")
- # box.prop(self, "offset")
- box.prop(self, "a0")
-
- def update(self, context, update_parent=False):
- props = self.find_in_selection(context)
- if props is not None:
- props.update(context, update_parent=update_parent)
-
-
-def update_operation(self, context):
- self.reverse(context, make_ccw=(self.operation == 'INTERSECTION'))
-
-
-def update_path(self, context):
- self.update_path(context)
-
-
-def update(self, context):
- self.update(context)
-
-
-def update_manipulators(self, context):
- self.update(context, manipulable_refresh=True)
-
-
-class ArchipackCutter():
- n_parts : IntProperty(
- name="Parts",
- min=1,
- default=1, update=update_manipulators
- )
- z : FloatProperty(
- name="dumb z",
- description="Dumb z for manipulator placeholder",
- default=0.01,
- options={'SKIP_SAVE'}
- )
- user_defined_path : StringProperty(
- name="User defined",
- update=update_path
- )
- user_defined_resolution : IntProperty(
- name="Resolution",
- min=1,
- max=128,
- default=12, update=update_path
- )
- operation : EnumProperty(
- items=(
- ('DIFFERENCE', 'Difference', 'Cut inside part', 0),
- ('INTERSECTION', 'Intersection', 'Keep inside part', 1)
- ),
- default='DIFFERENCE',
- update=update_operation
- )
- auto_update : BoolProperty(
- options={'SKIP_SAVE'},
- default=True,
- update=update_manipulators
- )
- # UI layout related
- parts_expand : BoolProperty(
- default=False
- )
-
- closed = True
-
- def draw(self, layout, context):
- box = layout.box()
- row = box.row()
- if self.parts_expand:
- row.prop(self, 'parts_expand', icon="TRIA_DOWN", text="Parts", emboss=False)
- box.prop(self, 'n_parts')
- for i, part in enumerate(self.parts):
- part.draw(layout, context, i)
- else:
- row.prop(self, 'parts_expand', icon="TRIA_RIGHT", text="Parts", emboss=False)
-
- def update_parts(self):
- # print("update_parts")
- # remove rows
- # NOTE:
- # n_parts+1
- # as last one is end point of last segment or closing one
- for i in range(len(self.parts), self.n_parts + 1, -1):
- self.parts.remove(i - 1)
-
- # add rows
- for i in range(len(self.parts), self.n_parts + 1):
- self.parts.add()
-
- self.setup_manipulators()
-
- def update_parent(self, context):
- raise NotImplementedError
-
- def setup_manipulators(self):
- 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)
- if n_manips < 5:
- s = p.manipulators.add()
- s.type_key = "SIZE"
- s.prop1_name = "offset"
- p.manipulators[2].prop1_name = str(i)
- p.manipulators[3].prop1_name = str(i + 1)
-
- def get_generator(self):
- g = CutterGenerator(self)
- for i, part in enumerate(self.parts):
- g.add_part(part)
- g.set_offset()
- g.close()
- return g
-
- 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 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 ensure_direction(self):
- # get segs ensure they are cw or ccw depending on operation
- # whatever the user do with points
- g = self.get_generator()
- pts = [seg.p0.to_3d() for seg in g.segs]
- if self.is_cw(pts) != (self.operation == 'INTERSECTION'):
- return g
- g.segs = [s.oposite for s in reversed(g.segs)]
- return g
-
- def from_spline(self, context, 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) == (self.operation == 'INTERSECTION'):
- pts = list(reversed(pts))
-
- pt = wM.inverted() @ pts[0]
-
- # pretranslate
- o = self.find_in_selection(context, self.auto_update)
- o.matrix_world = wM @ Matrix.Translation(pt)
- self.auto_update = False
- self.from_points(pts)
- self.auto_update = True
- self.update_parent(context, o)
-
- def from_points(self, pts):
-
- self.n_parts = len(pts) - 2
-
- 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
- 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
-
- def reverse(self, context, make_ccw=False):
-
- o = self.find_in_selection(context, self.auto_update)
-
- g = self.get_generator()
-
- pts = [seg.p0.to_3d() for seg in g.segs]
-
- if self.is_cw(pts) != make_ccw:
- return
-
- types = [p.type for p in self.parts]
-
- pts.append(pts[0])
-
- pts = list(reversed(pts))
- self.auto_update = False
-
- self.from_points(pts)
-
- for i, type in enumerate(reversed(types)):
- self.parts[i].type = type
- self.auto_update = True
- self.update_parent(context, o)
-
- def update_path(self, context):
-
- user_def_path = context.scene.objects.get(self.user_defined_path.strip())
- if user_def_path is not None and user_def_path.type == 'CURVE':
- self.from_spline(context,
- user_def_path.matrix_world,
- self.user_defined_resolution,
- user_def_path.data.splines[0])
-
- def make_surface(self, o, verts, edges):
- bm = bmesh.new()
- 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]]))
- bm.edges.new((bm.verts[-1], bm.verts[0]))
- bm.edges.ensure_lookup_table()
- bm.to_mesh(o.data)
- bm.free()
-
- def update(self, context, manipulable_refresh=False, update_parent=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 = []
- edges = []
-
- g = self.get_generator()
- g.locate_manipulators()
-
- # vertex index in order to build axis
- g.get_verts(verts, edges)
-
- if len(verts) > 2:
- self.make_surface(o, verts, edges)
-
- # enable manipulators rebuild
- if manipulable_refresh:
- self.manipulable_refresh = True
-
- # update parent on direct edit
- if manipulable_refresh or update_parent:
- self.update_parent(context, o)
-
- # restore context
- self.restore_context(context)
-
- def manipulable_setup(self, context):
-
- self.manipulable_disable(context)
- o = context.object
-
- n_parts = self.n_parts + 1
-
- 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
- self.manip_stack.append(part.manipulators[1].setup(context, o, part))
- # index
- self.manip_stack.append(part.manipulators[3].setup(context, o, self))
- # offset
- # self.manip_stack.append(part.manipulators[4].setup(context, o, part))
-
- # snap point
- self.manip_stack.append(part.manipulators[2].setup(context, o, self))
diff --git a/archipack/archipack_door.py b/archipack/archipack_door.py
deleted file mode 100644
index 4d8b61ca..00000000
--- a/archipack/archipack_door.py
+++ /dev/null
@@ -1,1899 +0,0 @@
-# -*- 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 .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, ArchipackDrawTool, ArchipackCollectionManager
-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='Bevel',
- 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="# h",
- min=1,
- max=50,
- default=1,
- description="panels h"
- )
- panels_y : IntProperty(
- name="# 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)
-
- 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:
- self.unlink_object_from_scene(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(ArchipackCollectionManager, 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='Bevel',
- 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="# h",
- min=1,
- max=50,
- default=1,
- description="panels h"
- )
- panels_y : IntProperty(
- name="# 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'
- )
- material : StringProperty(
- default=""
- )
-
- def draw(self, context):
- layout = self.layout
- row = layout.row()
- row.label(text="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
- self.link_object_to_scene(context, 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_set(state=True)
- context.view_layer.objects.active = o
- m = o.archipack_material.add()
- m.category = "door"
- m.material = self.material
- 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_set(state=True)
- context.view_layer.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(text="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_set(state=True)
- context.view_layer.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='Bevel',
- 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="# h",
- min=1,
- max=50,
- default=1,
- description="panels h", update=update_childs,
- )
- panels_y : IntProperty(
- name="# 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
- self.unlink_object_from_scene(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:
- self.unlink_object_from_scene(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,
- material=o.archipack_material[0].material
- )
- 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,
- material=o.archipack_material[0].material
- )
- 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)
- self.unlink_object_from_scene(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)
- self.link_object_to_scene(context, 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()
- m = p.archipack_material.add()
- m.category = 'door'
- m.material = o.archipack_material[0].material
- 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:
- self.unlink_object_from_scene(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
- self.link_object_to_scene(context, 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_set(state=True)
- context.view_layer.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,
- material=o.archipack_material[0].material
- )
- 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_set(state=True)
- context.view_layer.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)
- self.link_object_to_scene(context, hole_obj)
- hole_obj['archipack_hole'] = True
- hole_obj.parent = o
- hole_obj.matrix_world = o.matrix_world.copy()
-
- hole_obj.data.materials.clear()
- for mat in o.data.materials:
- hole_obj.data.materials.append(mat)
-
- 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
- self.link_object_to_scene(context, 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)
-
- o.select_set(state=True)
- context.view_layer.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='VIEW_PAN')
- 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='ADD')
- row.operator("archipack.door_preset", text="", icon='REMOVE').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
- self.link_object_to_scene(context, o)
- o.select_set(state=True)
- context.view_layer.objects.active = o
- self.add_material(o)
- self.load_preset(d)
- o.select_set(state=True)
- context.view_layer.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:
- self.unlink_object_from_scene(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:
- self.unlink_object_from_scene(handle)
- bpy.data.objects.remove(handle, do_unlink=True)
- self.unlink_object_from_scene(child)
- bpy.data.objects.remove(child, do_unlink=True)
- self.unlink_object_from_scene(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_set(state=True)
- context.view_layer.objects.active = o
-
- def unique(self, context):
- act = context.active_object
- sel = context.selected_objects[:]
- bpy.ops.object.select_all(action="DESELECT")
- for o in sel:
- if archipack_door.filter(o):
- o.select_set(state=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_set(state=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.view_layer.objects.active = act
- for o in sel:
- o.select_set(state=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_set(state=True)
- context.view_layer.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(ArchipackDrawTool, 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 = []
- object_name = ""
-
- @classmethod
- def poll(cls, context):
- return True
-
- def draw(self, context):
- layout = self.layout
- row = layout.row()
- row.label(text="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_set(state=True)
- context.view_layer.objects.active = o
-
- if event.shift:
- bpy.ops.archipack.door(mode="UNIQUE")
-
- new_w = o.copy()
- new_w.data = o.data
- self.link_object_to_scene(context, new_w)
- # instance subs
- for child in o.children:
- if "archipack_hole" not in child:
- new_c = child.copy()
- new_c.data = child.data
- new_c.parent = new_w
- self.link_object_to_scene(context, new_c)
- # dup handle if any
- for c in child.children:
- new_h = c.copy()
- new_h.data = c.data
- new_h.parent = new_c
- self.link_object_to_scene(context, new_h)
-
- o = new_w
- o.select_set(state=True)
- context.view_layer.objects.active = o
-
- else:
- bpy.ops.archipack.door(auto_manipulate=False, filepath=self.filepath)
- o = context.active_object
-
- self.object_name = o.name
-
- bpy.ops.archipack.generate_hole('INVOKE_DEFAULT')
- o.select_set(state=True)
- context.view_layer.objects.active = o
-
- def modal(self, context, event):
-
- context.area.tag_redraw()
- o = context.scene.objects.get(self.object_name.strip())
- if o is None:
- return {'FINISHED'}
-
- 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_viewport = True
- hole.hide_viewport = True
-
- res, tM, wall, width, y, z_offset = self.mouse_hover_wall(context, event)
-
- if hole is not None:
- o.hide_viewport = False
- hole.hide_viewport = 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 {'C'}:
- bpy.ops.archipack.door(mode='DELETE')
- self.feedback.disable()
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- bpy.ops.archipack.door_preset_menu(
- 'INVOKE_DEFAULT',
- preset_operator="archipack.door_draw")
- return {'FINISHED'}
-
- if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}:
- if wall is not None:
- o.select_set(state=True)
- context.view_layer.objects.active = wall
- wall.select_set(state=True)
- if bpy.ops.archipack.single_boolean.poll():
- bpy.ops.archipack.single_boolean()
- wall.select_set(state=False)
- # o must be a door here
- if d is not None:
- context.view_layer.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.view_layer.objects.active = last
- bpy.ops.archipack.door(mode="DELETE")
- context.view_layer.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.view_layer.objects.active = None
- bpy.ops.object.select_all(action="DESELECT")
- if o is not None:
- o.select_set(state=True)
- context.view_layer.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'),
- ('C', 'Choose another door'),
- ('SHIFT', 'Make independent 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):
- 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
deleted file mode 100644
index 2cd9e227..00000000
--- a/archipack/archipack_fence.py
+++ /dev/null
@@ -1,1785 +0,0 @@
-# -*- 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
-
- 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 which 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, don't 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.dot(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':
- # can't 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':
- # can't 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.dot(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:
- if "C_" in self.type:
- p = Vector((0, 0))
- v = self.length * Vector((cos(self.a0), sin(self.a0)))
- w = StraightFence(p, v)
- a0 = pi / 2
- else:
- c = -self.radius * Vector((cos(self.a0), sin(self.a0)))
- w = CurvedFence(c, self.radius, self.a0, pi)
-
- # not closed, see wall
- # for closed ability
- 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 = 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="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
- )
- 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 = 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="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="Spacing",
- 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="#",
- 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.Translation(pt)
-
- def update_path(self, context):
- path = context.scene.objects.get(self.user_defined_path.strip())
- 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.strip())
- 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.strip())
- 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='VIEW_PAN')
- 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='ADD')
- row.operator("archipack.fence_preset", text="", icon='REMOVE').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 != "":
- 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", 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", text="Parts", emboss=False)
-
- box = layout.box()
- row = box.row(align=True)
- if prop.handrail_expand:
- row.prop(prop, 'handrail_expand', icon="TRIA_DOWN", text="Handrail", emboss=False)
- else:
- row.prop(prop, 'handrail_expand', icon="TRIA_RIGHT", 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", text="Post", emboss=False)
- else:
- row.prop(prop, 'post_expand', icon="TRIA_RIGHT", 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", text="Subs", emboss=False)
- else:
- row.prop(prop, 'subs_expand', icon="TRIA_RIGHT", 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", text="Panels", emboss=False)
- else:
- row.prop(prop, 'panel_expand', icon="TRIA_RIGHT", 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", text="Rails", emboss=False)
- else:
- row.prop(prop, 'rail_expand', icon="TRIA_RIGHT", 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", 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", 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
- self.link_object_to_scene(context, o)
- o.select_set(state=True)
- context.view_layer.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 = context.scene.cursor.location
- o.select_set(state=True)
- context.view_layer.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(text="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(text="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_set(state=True)
- context.view_layer.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_description = "Show Fence Presets"
- 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
deleted file mode 100644
index 6f0244be..00000000
--- a/archipack/archipack_floor.py
+++ /dev/null
@@ -1,2102 +0,0 @@
-# -*- 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: Jacob Morris - Stephen Leger (s-leger)
-# ----------------------------------------------------------
-
-import bpy
-from bpy.types import Operator, PropertyGroup, Mesh, Panel
-from bpy.props import (
- FloatProperty, CollectionProperty, StringProperty,
- BoolProperty, IntProperty, EnumProperty
- )
-from mathutils import Vector, Matrix
-from mathutils.geometry import interpolate_bezier
-from random import uniform
-from math import radians, cos, sin, pi, atan2, sqrt
-import bmesh
-from .bmesh_utils import BmeshEdit as bmed
-from .archipack_2d import Line, Arc
-from .archipack_manipulator import Manipulable, archipack_manipulator
-from .archipack_preset import ArchipackPreset, PresetMenuOperator
-from .archipack_object import ArchipackCreateTool, ArchipackObject
-from .archipack_cutter import (
- CutAblePolygon, CutAbleGenerator,
- ArchipackCutter,
- ArchipackCutterPart
- )
-
-
-# ------------------------------------------------------------------
-# Define property class to store object parameters and update mesh
-# ------------------------------------------------------------------
-
-
-class Floor():
-
- 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_floor(self, a0, length):
- s = self.straight(length).rotate(a0)
- return StraightFloor(s.p, s.v)
-
- def curved_floor(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 CurvedFloor(c, radius, a0, da)
-
-
-class StraightFloor(Floor, Line):
-
- def __init__(self, p, v):
- Line.__init__(self, p, v)
- Floor.__init__(self)
-
-
-class CurvedFloor(Floor, Arc):
-
- def __init__(self, c, radius, a0, da):
- Arc.__init__(self, c, radius, a0, da)
- Floor.__init__(self)
-
-
-class FloorGenerator(CutAblePolygon, CutAbleGenerator):
-
- def __init__(self, parts):
- self.parts = parts
- self.segs = []
- self.holes = []
- self.convex = True
- self.xsize = 0
-
- def add_part(self, part):
-
- if len(self.segs) < 1:
- s = None
- else:
- s = self.segs[-1]
- # start a new floor
- if s is None:
- if part.type == 'S_SEG':
- p = Vector((0, 0))
- v = part.length * Vector((cos(part.a0), sin(part.a0)))
- s = StraightFloor(p, v)
- elif part.type == 'C_SEG':
- c = -part.radius * Vector((cos(part.a0), sin(part.a0)))
- s = CurvedFloor(c, part.radius, part.a0, part.da)
- else:
- if part.type == 'S_SEG':
- s = s.straight_floor(part.a0, part.length)
- elif part.type == 'C_SEG':
- s = s.curved_floor(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:
- 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].line)
-
- 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__ == "StraightFloor":
- # 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 s in self.segs:
- if "Curved" in type(s).__name__:
- for i in range(16):
- # x, y = floor.line.lerp(i / 16)
- verts.append(s.lerp(i / 16).to_3d())
- else:
- # x, y = s.line.p0
- verts.append(s.p0.to_3d())
- """
- for i in range(33):
- x, y = floor.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 limits(self):
- x_size = [s.p0.x for s in self.segs]
- y_size = [s.p0.y for s in self.segs]
- for s in self.segs:
- if "Curved" in type(s).__name__:
- x_size.append(s.c.x + s.r)
- x_size.append(s.c.x - s.r)
- y_size.append(s.c.y + s.r)
- y_size.append(s.c.y - s.r)
-
- self.xmin = min(x_size)
- self.xmax = max(x_size)
- self.xsize = self.xmax - self.xmin
- self.ymin = min(y_size)
- self.ymax = max(y_size)
- self.ysize = self.ymax - self.ymin
-
- def cut(self, context, o):
- """
- either external or holes cuts
- """
- self.limits()
- self.as_lines()
- self.is_convex()
- for b in o.children:
- d = archipack_floor_cutter.datablock(b)
- if d is not None:
- g = d.ensure_direction()
- g.change_coordsys(b.matrix_world, o.matrix_world)
- self.slice(g)
-
- def floor(self, context, o, d):
-
- verts, faces, matids, uvs = [], [], [], []
-
- if d.bevel:
- bevel = d.bevel_amount
- else:
- bevel = 0
-
- if d.add_grout:
- thickness = min(d.thickness - d.mortar_depth, d.thickness - 0.0001)
- bottom = min(d.thickness - (d.mortar_depth + bevel), d.thickness - 0.0001)
- else:
- thickness = d.thickness
- bottom = 0
-
- self.top = d.thickness
-
- self.generate_pattern(d, verts, faces, matids, uvs)
- bm = bmed.buildmesh(
- context, o, verts, faces, matids=matids, uvs=uvs,
- weld=False, clean=False, auto_smooth=True, temporary=True)
-
- self.cut_holes(bm, self)
- self.cut_boundary(bm, self)
-
- bmesh.ops.dissolve_limit(bm,
- angle_limit=0.01,
- use_dissolve_boundaries=False,
- verts=bm.verts,
- edges=bm.edges,
- delimit={'MATERIAL'})
-
- bm.verts.ensure_lookup_table()
-
- if d.solidify:
- # solidify and floor bottom
- geom = bm.faces[:]
- verts = bm.verts[:]
- edges = bm.edges[:]
- bmesh.ops.solidify(bm, geom=geom, thickness=0.0001)
- for v in verts:
- v.co.z = bottom
-
- bm.normal_update()
-
- # bevel
- if d.bevel:
- for v in bm.verts:
- v.select = True
- for v in verts:
- v.select = False
- for v in bm.edges:
- v.select = True
- for v in edges:
- v.select = False
- geom = [v for v in bm.verts if v.select]
- geom.extend([v for v in bm.edges if v.select])
- bmesh.ops.bevel(bm,
- geom=geom,
- offset=d.bevel_amount,
- offset_type='OFFSET',
- segments=1, # d.bevel_res
- profile=0.5,
- # vertex_only=False,
- clamp_overlap=False,
- material=-1)
-
- bm.to_mesh(o.data)
- bm.free()
-
- # Grout
- if d.add_grout:
- verts = []
- self.get_verts(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)
-
- self.cut_holes(bm, self)
- self.cut_boundary(bm, self)
-
- bmesh.ops.dissolve_limit(bm,
- angle_limit=0.01,
- use_dissolve_boundaries=False,
- verts=bm.verts,
- edges=bm.edges,
- delimit={'MATERIAL'})
-
- bm.verts.ensure_lookup_table()
-
- geom = bm.faces[:]
- bmesh.ops.solidify(bm, geom=geom, thickness=thickness)
- bmed.bmesh_join(context, o, [bm], normal_update=True)
-
- bpy.ops.object.mode_set(mode='OBJECT')
-
- # ---------------------------------------------------
- # Patterns
- # ---------------------------------------------------
-
- def regular_tile(self, d, verts, faces, matids, uvs):
- """
- ____ ____ ____
- | || || | Regular tile, rows can be offset, either manually or randomly
- |____||____||____|
- ____ ____ ____
- | || || |
- |____||____||____|
- """
- off = False
- o = 1 / (100 / d.offset) if d.offset != 0 else 0
- y = self.ymin
-
- while y < self.ymax:
- x = self.xmin
- tl2 = d.tile_length
- if y < self.ymax < y + d.tile_length:
- tl2 = self.ymax - y
-
- while x < self.xmax:
- tw2 = d.tile_width
-
- if x < self.xmax < x + d.tile_width:
- tw2 = self.xmax - x
- elif x == self.xmin and off and not d.random_offset:
- tw2 = d.tile_width * o
- elif x == self.xmin and d.random_offset:
- v = d.tile_width * d.offset_variance * 0.0049
- tw2 = (d.tile_width / 2) + uniform(-v, v)
-
- self.add_plane(d, verts, faces, matids, uvs, x, y, tw2, tl2)
- x += tw2 + d.spacing
-
- y += tl2 + d.spacing
- off = not off
-
- def hopscotch(self, d, verts, faces, matids, uvs):
- """
- ____ _ Large tile, plus small one on top right corner
- | ||_|
- |____| ____ _ But shifted up so next large one is right below previous small one
- | ||_|
- |____|
- """
- sp = d.spacing
-
- # movement variables
- row = 0
-
- tw = d.tile_width
- tl = d.tile_length
- s_tw = (tw - sp) / 2 # small tile width
- s_tl = (tl - sp) / 2 # small tile length
- y = self.ymin - s_tl
-
- pre_y = y
- while y < self.ymax + s_tl or (row == 2 and y - sp < self.ymax):
- x = self.xmin
- step_back = True
-
- if row == 1: # row start indented slightly
- x = self.xmin + s_tw + sp
-
- while x < self.xmax:
- if row == 0 or row == 1:
- # adjust for if there is a need to cut off the bottom of the tile
- if y < self.ymin - s_tl:
- self.add_plane(d, verts, faces, matids, uvs, x, y, tw, tl + y - self.ymin) # large one
- else:
- self.add_plane(d, verts, faces, matids, uvs, x, y, tw, tl) # large one
-
- self.add_plane(d, verts, faces, matids, uvs, x + tw + sp, y + s_tl + sp, s_tw, s_tl) # small one
-
- if step_back:
- x += tw + sp
- y -= s_tl + sp
- else:
- x += tw + s_tw + 2 * sp
- y += s_tl + sp
-
- step_back = not step_back
- else:
- if x == self.xmin: # half width for starting position
- self.add_plane(d, verts, faces, matids, uvs, x, y, s_tw, tl) # large one
- # small one on right
- self.add_plane(d, verts, faces, matids, uvs, x + s_tw + sp, y + s_tl + sp, s_tw, s_tl)
- # small one on bottom
- self.add_plane(d, verts, faces, matids, uvs, x, y - sp - s_tl, s_tw, s_tl)
- x += (2 * s_tw) + tw + (3 * sp)
- else:
- self.add_plane(d, verts, faces, matids, uvs, x, y, tw, tl) # large one
- # small one on right
- self.add_plane(d, verts, faces, matids, uvs, x + tw + sp, y + s_tl + sp, s_tw, s_tl)
- x += (2 * tw) + (3 * sp) + s_tw
-
- if row == 0 or row == 2:
- y = pre_y + tl + sp
- else:
- y = pre_y + s_tl + sp
- pre_y = y
-
- row = (row + 1) % 3 # keep wrapping rows
-
- def stepping_stone(self, d, verts, faces, matids, uvs):
- """
- ____ __ ____
- | ||__|| | Row of large one, then two small ones stacked beside it
- | | __ | |
- |____||__||____|
- __ __ __ __
- |__||__||__||__| Row of smalls
- """
- sp = d.spacing
- y = self.ymin
- row = 0
-
- tw = d.tile_width
- tl = d.tile_length
- s_tw = (tw - sp) / 2
- s_tl = (tl - sp) / 2
-
- while y < self.ymax:
- x = self.xmin
-
- while x < self.xmax:
- if row == 0: # large one then two small ones stacked beside it
- self.add_plane(d, verts, faces, matids, uvs, x, y, tw, tl)
- self.add_plane(d, verts, faces, matids, uvs, x + tw + sp, y, s_tw, s_tl,)
- self.add_plane(d, verts, faces, matids, uvs, x + tw + sp, y + s_tl + sp, s_tw, s_tl)
- x += tw + s_tw + (2 * sp)
- else: # row of small ones
- self.add_plane(d, verts, faces, matids, uvs, x, y, s_tw, s_tl)
- self.add_plane(d, verts, faces, matids, uvs, x + s_tw + sp, y, s_tw, s_tl)
- x += tw + sp
-
- if row == 0:
- y += tl + sp
- else:
- y += s_tl + sp
-
- row = (row + 1) % 2
-
- def hexagon(self, d, verts, faces, matids, uvs):
- """
- __ Hexagon tiles
- / \
- \___/
- """
- sp = d.spacing
- width = d.tile_width
- dia = (width / 2) / cos(radians(30))
- # top of current, half way up next, vertical spacing component
- vertical_spacing = dia * (1 + sin(radians(30))) + (sp * sin(radians(60))) # center of one row to next row
- da = pi / 3
- base_points = [(sin(i * da), cos(i * da)) for i in range(6)]
-
- y = self.ymin
- offset = False
- while y - width / 2 < self.ymax: # place tile as long as bottom is still within bounds
- if offset:
- x = self.xmin + width / 2
- else:
- x = self.xmin - sp / 2
-
- while x - width / 2 < self.xmax: # place tile as long as left is still within bounds
- f = len(verts)
-
- if d.vary_thickness and d.thickness_variance > 0:
- v = d.thickness / 100 * d.thickness_variance
- z = uniform(self.top, self.top + v)
- else:
- z = self.top
-
- for pt in base_points:
- verts.append((dia * pt[0] + x, dia * pt[1] + y, z))
-
- faces.append([f] + [i for i in range(f + 1, len(verts))])
- uvs.append(base_points)
- self.add_matid(d, matids)
-
- x += width + sp
-
- y += vertical_spacing
- offset = not offset
-
- def windmill(self, d, verts, faces, matids, uvs):
- """
- __ ____
- | ||____| This also has a square one in the middle, totaling 5 tiles per pattern
- |__| __
- ____ | |
- |____||__|
- """
- sp = d.spacing
-
- tw = d.tile_width
- tl = d.tile_length
- s_tw = (tw - sp) / 2
- s_tl = (tl - sp) / 2
-
- y = self.ymin
- while y < self.ymax:
- x = self.xmin
-
- while x < self.xmax:
- self.add_plane(d, verts, faces, matids, uvs, x, y, tw, s_tl) # bottom
- self.add_plane(d, verts, faces, matids, uvs, x + tw + sp, y, s_tw, tl, rotate_uv=True) # right
- self.add_plane(d, verts, faces, matids, uvs, x + s_tw + sp, y + tl + sp, tw, s_tl) # top
- self.add_plane(d, verts, faces, matids, uvs, x, y + s_tl + sp, s_tw, tl, rotate_uv=True) # left
- self.add_plane(d, verts, faces, matids, uvs, x + s_tw + sp, y + s_tl + sp, s_tw, s_tl) # center
-
- x += tw + s_tw + (2 * sp)
- y += tl + s_tl + (2 * sp)
-
- def boards(self, d, verts, faces, matids, uvs):
- """
- ||| Typical wood boards
- |||
- """
- x = self.xmin
- bw, bl = d.board_width, d.board_length
- off = False
- o = 1 / (100 / d.offset) if d.offset != 0 else 0
-
- while x < self.xmax:
- if d.vary_width:
- v = bw * (d.width_variance / 100) * 0.99
- bw2 = bw + uniform(-v, v)
- else:
- bw2 = bw
-
- if bw2 + x > self.xmax:
- bw2 = self.xmax - x
- y = self.ymin
-
- counter = 1
- while y < self.ymax:
- bl2 = bl
- if d.vary_length:
- v = bl * (d.length_variance / 100) * 0.99
- bl2 = bl + uniform(-v, v)
- elif y == self.ymin and off and not d.random_offset:
- bl2 = bl * o
- elif y == self.ymin and d.random_offset:
- v = bl * d.offset_variance * 0.0049
- bl2 = (bl / 2) + uniform(-v, v)
-
- if (counter >= d.max_boards and d.vary_length) or y + bl2 > self.ymax:
- bl2 = self.ymax - y
-
- self.add_plane(d, verts, faces, matids, uvs, x, y, bw2, bl2, rotate_uv=True)
- y += bl2 + d.length_spacing
- counter += 1
- off = not off
- x += bw2 + d.width_spacing
-
- def square_parquet(self, d, verts, faces, matids, uvs):
- """
- ||--||-- Alternating groups oriented either horizontally, or forwards and backwards.
- ||--||-- self.spacing is used because it is the same spacing for width and length
- --||--|| Board width is calculated using number of boards and the length.
- --||--||
- """
- x = self.xmin
- start_orient_length = True
-
- # figure board width
- bl = d.short_board_length
- bw = (bl - (d.boards_in_group - 1) * d.spacing) / d.boards_in_group
- while x < self.xmax:
- y = self.ymin
- orient_length = start_orient_length
- while y < self.ymax:
-
- if orient_length:
- start_x = x
-
- for i in range(d.boards_in_group):
- if x < self.xmax and y < self.ymax:
- self.add_plane(d, verts, faces, matids, uvs, x, y, bw, bl, rotate_uv=True)
- x += bw + d.spacing
-
- x = start_x
- y += bl + d.spacing
-
- else:
- for i in range(d.boards_in_group):
- if x < self.xmax and y < self.ymax:
- self.add_plane(d, verts, faces, matids, uvs, x, y, bl, bw)
- y += bw + d.spacing
-
- orient_length = not orient_length
-
- start_orient_length = not start_orient_length
- x += bl + d.spacing
-
- def herringbone(self, d, verts, faces, matids, uvs):
- """
- Boards are at 45 degree angle, in chevron pattern, ends are angled
- """
- width_dif = d.board_width / cos(radians(45))
- x_dif = d.short_board_length * cos(radians(45))
- y_dif = d.short_board_length * sin(radians(45))
- total_y_dif = width_dif + y_dif
- sp_dif = d.spacing / cos(radians(45))
-
- y = self.ymin - y_dif
- while y < self.ymax:
- x = self.xmin
-
- while x < self.xmax:
- # left side
-
- self.add_face(d, verts, faces, matids, uvs,
- (x, y, 0), (x + x_dif, y + y_dif, 0),
- (x + x_dif, y + total_y_dif, 0), (x, y + width_dif, 0))
-
- x += x_dif + d.spacing
-
- # right side
- if x < self.xmax:
- self.add_face(d, verts, faces, matids, uvs,
- (x, y + y_dif, 0), (x + x_dif, y, 0),
- (x + x_dif, y + width_dif, 0), (x, y + total_y_dif, 0))
- x += x_dif + d.spacing
-
- y += width_dif + sp_dif # adjust spacing amount for 45 degree angle
-
- def herringbone_parquet(self, d, verts, faces, matids, uvs):
- """
- Boards are at 45 degree angle, in chevron pattern, ends are square, not angled
- """
-
- an_45 = 0.5 * sqrt(2)
-
- x_dif = d.short_board_length * an_45
- y_dif = d.short_board_length * an_45
- y_dif_45 = d.board_width * an_45
- x_dif_45 = d.board_width * an_45
- total_y_dif = y_dif + y_dif_45
-
- sp_dif = (d.spacing / an_45) / 2 # divide by two since it is used for both x and y
- width_dif = d.board_width / an_45
-
- y = self.ymin - y_dif
- while y - y_dif_45 < self.ymax: # continue as long as bottom left corner is still good
- x = self.xmin
-
- while x - x_dif_45 < self.xmax: # continue as long as top left corner is still good
- # left side
-
- self.add_face(d, verts, faces, matids, uvs,
- (x, y, 0),
- (x + x_dif, y + y_dif, 0),
- (x + x_dif - x_dif_45, y + total_y_dif, 0),
- (x - x_dif_45, y + y_dif_45, 0))
-
- x += x_dif - x_dif_45 + sp_dif
- y0 = y + y_dif - y_dif_45 - sp_dif
-
- if x < self.xmax:
- self.add_face(d, verts, faces, matids, uvs,
- (x, y0, 0),
- (x + x_dif, y0 - y_dif, 0),
- (x + x_dif + x_dif_45, y0 - y_dif + y_dif_45, 0),
- (x + x_dif_45, y0 + y_dif_45, 0))
-
- x += x_dif + x_dif_45 + sp_dif
-
- else: # we didn't place the right board, so step ahead far enough the the while loop for x breaks
- break
-
- y += width_dif + (2 * sp_dif)
-
- def add_matid(self, d, matids):
- if d.vary_materials:
- matid = uniform(1, d.matid)
- else:
- matid = d.matid
- matids.append(matid)
-
- def add_plane(self, d, verts, faces, matids, uvs, x, y, w, l, rotate_uv=False):
- """
- Adds vertices and faces for a place, clip to outer boundaries if clip is True
- :param x: start x position
- :param y: start y position
- :param w: width (in x direction)
- :param l: length (in y direction)
- """
-
- x1 = x + w
- y1 = y + l
-
- if d.vary_thickness and d.thickness_variance > 0:
- v = d.thickness / 100 * d.thickness_variance
- z = uniform(self.top, self.top + v)
- else:
- z = self.top
-
- p = len(verts)
- verts.extend([(x, y, z), (x1, y, z), (x1, y1, z), (x, y1, z)])
- faces.append([p + 3, p + 2, p + 1, p])
- if rotate_uv:
- uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)])
- else:
- uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
- self.add_matid(d, matids)
-
- def add_face(self, d, verts, faces, matids, uvs, p0, p1, p2, p3):
- """
- Adds vertices and faces for a place, clip to outer boundaries if clip is True
- :param x: start x position
- :param y: start y position
- :param w: width (in x direction)
- :param l: length (in y direction)
- """
-
- if d.vary_thickness and d.thickness_variance > 0:
- v = d.thickness / 100 * d.thickness_variance
- z = uniform(self.top, self.top + v)
- else:
- z = self.top
-
- p = len(verts)
- verts.extend([(v[0], v[1], z) for v in [p0, p1, p2, p3]])
- faces.append([p + 3, p + 2, p + 1, p])
- uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
- self.add_matid(d, matids)
-
- def add_manipulator(self, name, pt1, pt2, pt3):
- m = self.manipulators.add()
- m.prop1_name = name
- m.set_pts([pt1, pt2, pt3])
-
- def generate_pattern(self, d, verts, faces, matids, uvs):
-
- if d.pattern == "boards":
- self.boards(d, verts, faces, matids, uvs)
- elif d.pattern == "square_parquet":
- self.square_parquet(d, verts, faces, matids, uvs)
- elif d.pattern == "herringbone":
- self.herringbone(d, verts, faces, matids, uvs)
- elif d.pattern == "herringbone_parquet":
- self.herringbone_parquet(d, verts, faces, matids, uvs)
- elif d.pattern == "regular_tile":
- self.regular_tile(d, verts, faces, matids, uvs)
- elif d.pattern == "hopscotch":
- self.hopscotch(d, verts, faces, matids, uvs)
- elif d.pattern == "stepping_stone":
- self.stepping_stone(d, verts, faces, matids, uvs)
- elif d.pattern == "hexagon":
- self.hexagon(d, verts, faces, matids, uvs)
- elif d.pattern == "windmill":
- self.windmill(d, verts, faces, matids, uvs)
-
-
-def update(self, context):
- self.update(context)
-
-
-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_floor(part.a0, part.length)
- else:
- w = w0.curved_floor(part.a0, part.da, part.radius)
- else:
- if "C_" in self.type:
- p = Vector((0, 0))
- v = self.length * Vector((cos(self.a0), sin(self.a0)))
- w = StraightFloor(p, v)
- a0 = pi / 2
- else:
- c = -self.radius * Vector((cos(self.a0), sin(self.a0)))
- w = CurvedFloor(c, self.radius, self.a0, pi)
-
- # w0 - w - w1
- if idx + 1 == d.n_parts:
- dp = - w.p0
- else:
- 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
-
-
-def update_manipulators(self, context):
- self.update(context, manipulable_refresh=True)
-
-
-def update_path(self, context):
- self.update_path(context)
-
-
-class archipack_floor_part(PropertyGroup):
-
- """
- A single manipulable polyline like segment
- polyline like segment line or arc based
- """
- 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="Side offset of segment",
- default=0,
- unit='LENGTH', subtype='DISTANCE',
- update=update
- )
- manipulators : CollectionProperty(type=archipack_manipulator)
-
- def find_in_selection(self, context):
- """
- find witch selected object this instance belongs to
- provide support for "copy to selected"
- """
- selected = context.selected_objects[:]
- for o in selected:
- props = archipack_floor.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_in_selection(context)
- if props is not None:
- props.update(context, manipulable_refresh)
-
- def draw(self, context, layout, index):
- box = layout.box()
- # box.prop(self, "type", text=str(index + 1))
- box.label(text="#" + str(index + 1))
- if self.type in ['C_SEG']:
- box.prop(self, "radius")
- box.prop(self, "da")
- else:
- box.prop(self, "length")
- box.prop(self, "a0")
-
-
-class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
- n_parts : IntProperty(
- name="Parts",
- min=1,
- default=1, update=update_manipulators
- )
- parts : CollectionProperty(type=archipack_floor_part)
- user_defined_path : StringProperty(
- name="User defined",
- update=update_path
- )
- user_defined_resolution : IntProperty(
- name="Resolution",
- min=1,
- max=128,
- default=12, update=update_path
- )
- closed : BoolProperty(
- default=True,
- name="Close",
- options={'SKIP_SAVE'},
- update=update_manipulators
- )
- # UI layout related
- parts_expand : BoolProperty(
- options={'SKIP_SAVE'},
- default=False
- )
-
- pattern : EnumProperty(
- name='Floor Pattern',
- items=(("boards", "Boards", ""),
- ("square_parquet", "Square Parquet", ""),
- ("herringbone_parquet", "Herringbone Parquet", ""),
- ("herringbone", "Herringbone", ""),
- ("regular_tile", "Regular Tile", ""),
- ("hopscotch", "Hopscotch", ""),
- ("stepping_stone", "Stepping Stone", ""),
- ("hexagon", "Hexagon", ""),
- ("windmill", "Windmill", "")),
- default="boards",
- update=update
- )
- spacing : FloatProperty(
- name='Spacing',
- description='The amount of space between boards or tiles in both directions',
- unit='LENGTH', subtype='DISTANCE',
- min=0,
- default=0.005,
- precision=2,
- update=update
- )
- thickness : FloatProperty(
- name='Thickness',
- description='Thickness',
- unit='LENGTH', subtype='DISTANCE',
- min=0.0,
- default=0.005,
- precision=2,
- update=update
- )
- vary_thickness : BoolProperty(
- name='Random Thickness',
- description='Vary thickness',
- default=False,
- update=update
- )
- thickness_variance : FloatProperty(
- name='Variance',
- description='How much vary by',
- min=0, max=100,
- default=25,
- precision=2,
- subtype='PERCENTAGE',
- update=update
- )
-
- board_width : FloatProperty(
- name='Width',
- description='The width',
- unit='LENGTH', subtype='DISTANCE',
- min=0.02,
- default=0.2,
- precision=2,
- update=update
- )
- vary_width : BoolProperty(
- name='Random Width',
- description='Vary width',
- default=False,
- update=update
- )
- width_variance : FloatProperty(
- name='Variance',
- description='How much vary by',
- subtype='PERCENTAGE',
- min=1, max=100, default=50,
- precision=2,
- update=update
- )
- width_spacing : FloatProperty(
- name='Width Spacing',
- description='The amount of space between boards in the width direction',
- unit='LENGTH', subtype='DISTANCE',
- min=0,
- default=0.002,
- precision=2,
- update=update
- )
-
- board_length : FloatProperty(
- name='Length',
- description='The length of the boards',
- unit='LENGTH', subtype='DISTANCE',
- precision=2,
- min=0.02,
- default=2,
- update=update
- )
- short_board_length : FloatProperty(
- name='Length',
- description='The length of the boards',
- unit='LENGTH', subtype='DISTANCE',
- precision=2,
- min=0.02,
- default=2,
- update=update
- )
- vary_length : BoolProperty(
- name='Random Length',
- description='Vary board length',
- default=False,
- update=update
- )
- length_variance : FloatProperty(
- name='Variance',
- description='How much board length can vary by',
- subtype='PERCENTAGE',
- min=1, max=100, default=50,
- precision=2, update=update
- )
- max_boards : IntProperty(
- name='Max Boards',
- description='Max number of boards in one row',
- min=1,
- default=20,
- update=update
- )
- length_spacing : FloatProperty(
- name='Length Spacing',
- description='The amount of space between boards in the length direction',
- unit='LENGTH', subtype='DISTANCE',
- min=0,
- default=0.002,
- precision=2,
- update=update
- )
-
- # parquet specific
- boards_in_group : IntProperty(
- name='Boards in Group',
- description='Number of boards in a group',
- min=1, default=4,
- update=update
- )
-
- # tile specific
- tile_width : FloatProperty(
- name='Width',
- description='Width of the tiles',
- unit='LENGTH', subtype='DISTANCE',
- min=0.002,
- default=0.2,
- precision=2,
- update=update
- )
- tile_length : FloatProperty(
- name='Length',
- description='Length of the tiles',
- unit='LENGTH', subtype='DISTANCE',
- precision=2,
- min=0.02,
- default=0.3,
- update=update
- )
-
- # grout
- add_grout : BoolProperty(
- name='Add Grout',
- description='Add grout',
- default=False,
- update=update
- )
- mortar_depth : FloatProperty(
- name='Depth',
- description='The depth of the mortar from the surface of the tile',
- unit='LENGTH', subtype='DISTANCE',
- precision=2,
- step=0.005,
- min=0,
- default=0.001,
- update=update
- )
-
- # regular tile
- random_offset : BoolProperty(
- name='Random Offset',
- description='Random amount of offset for each row of tiles',
- update=update, default=False
- )
- offset : FloatProperty(
- name='Offset',
- description='How much to offset each row of tiles',
- min=0, max=100, default=0,
- precision=2,
- update=update
- )
- offset_variance : FloatProperty(
- name='Variance',
- description='How much to vary the offset each row of tiles',
- min=0.001, max=100, default=50,
- precision=2,
- update=update
- )
-
- # bevel
- bevel : BoolProperty(
- name='Bevel',
- update=update,
- default=False,
- description='Bevel upper faces'
- )
- bevel_amount : FloatProperty(
- name='Bevel',
- description='Bevel amount',
- unit='LENGTH', subtype='DISTANCE',
- min=0.0001, default=0.001,
- precision=2, step=0.05,
- update=update
- )
- solidify : BoolProperty(
- name="Solidify",
- default=True,
- update=update
- )
- vary_materials : BoolProperty(
- name="Random Material",
- default=True,
- description="Vary Material indexes",
- update=update)
- matid : IntProperty(
- name="#variations",
- min=1,
- max=10,
- default=7,
- description="Material index maxi",
- update=update)
- auto_update : BoolProperty(
- options={'SKIP_SAVE'},
- default=True,
- update=update_manipulators
- )
- z : FloatProperty(
- name="dumb z",
- description="Dumb z for manipulator placeholder",
- default=0.01,
- options={'SKIP_SAVE'}
- )
-
- def get_generator(self):
- g = FloorGenerator(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 update_parts(self, o):
-
- for i in range(len(self.parts), self.n_parts, -1):
- self.parts.remove(i - 1)
-
- # add rows
- for i in range(len(self.parts), self.n_parts):
- self.parts.add()
-
- self.setup_manipulators()
-
- g = self.get_generator()
-
- return g
-
- @staticmethod
- def create_uv_seams(bm):
- handled = set()
- for edge in bm.edges:
- if edge.verts[0].co.z == 0 and edge.verts[1].co.z == 0: # bottom
- # make sure both vertices on the edge haven't been handled, this forces one edge to not be made a seam
- # leaving the bottom face still attached
- if not (edge.verts[0].index in handled and edge.verts[1].index in handled):
- edge.seam = True
- handled.add(edge.verts[0].index)
- handled.add(edge.verts[1].index)
- elif edge.verts[0].co.z != edge.verts[1].co.z: # not horizontal, so they are vertical seams
- edge.seam = True
-
- 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, context, 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)
-
- pt = wM.inverted() @ pts[0]
-
- # pretranslate
- o = self.find_in_selection(context, self.auto_update)
- o.matrix_world = wM @ Matrix.Translation(pt)
- self.from_points(pts)
-
- def from_points(self, pts):
-
- 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 = True
- self.auto_update = True
-
- def update_path(self, context):
- user_def_path = context.scene.objects.get(self.user_defined_path.strip())
- if user_def_path is not None and user_def_path.type == 'CURVE':
- self.from_spline(
- context,
- user_def_path.matrix_world,
- self.user_defined_resolution,
- user_def_path.data.splines[0])
-
- def add_manipulator(self, name, pt1, pt2, pt3):
- m = self.manipulators.add()
- m.prop1_name = name
- m.set_pts([pt1, pt2, pt3])
-
- def update_manipulators(self):
- self.manipulators.clear() # clear every time, add new ones
- self.add_manipulator("length", (0, 0, 0), (0, self.length, 0), (-0.4, 0, 0))
- self.add_manipulator("width", (0, 0, 0), (self.width, 0, 0), (0.4, 0, 0))
-
- z = self.thickness
-
- if self.pattern == "boards":
- self.add_manipulator("board_length", (0, 0, z), (0, self.board_length, z), (0.1, 0, z))
- self.add_manipulator("board_width", (0, 0, z), (self.board_width, 0, z), (-0.2, 0, z))
- elif self.pattern == "square_parquet":
- self.add_manipulator("short_board_length", (0, 0, z), (0, self.short_board_length, z), (-0.2, 0, z))
- elif self.pattern in ("herringbone", "herringbone_parquet"):
- dia = self.short_board_length * cos(radians(45))
- dia2 = self.board_width * cos(radians(45))
- self.add_manipulator("short_board_length", (0, 0, z), (dia, dia, z), (0, 0, z))
- self.add_manipulator("board_width", (dia, 0, z), (dia - dia2, dia2, z), (0, 0, z))
- else:
- tl = self.tile_length
- tw = self.tile_width
-
- if self.pattern in ("regular_tile", "hopscotch", "stepping_stone"):
- self.add_manipulator("tile_width", (0, tl, z), (tw, tl, z), (0, 0, z))
- self.add_manipulator("tile_length", (0, 0, z), (0, tl, z), (0, 0, z))
- elif self.pattern == "hexagon":
- self.add_manipulator("tile_width", (tw / 2 + self.spacing, 0, z), (tw * 1.5 + self.spacing, 0, z),
- (0, 0, 0))
- elif self.pattern == "windmill":
- self.add_manipulator("tile_width", (0, 0, z), (tw, 0, 0), (0, 0, z))
- self.add_manipulator("tile_length", (0, tl / 2 + self.spacing, z), (0, tl * 1.5 + self.spacing, z),
- (0, 0, z))
-
- 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"
- p.manipulators[0].type_key = 'ANGLE'
- 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 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)
-
- g = self.update_parts(o)
-
- g.cut(context, o)
- g.floor(context, o, self)
-
- # 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
-
- self.manipulable_setup(context)
- self.manipulate_mode = True
-
- self._manipulable_invoke(context)
-
- return True
-
-
-def update_hole(self, context):
- self.update(context, update_parent=True)
-
-
-def update_operation(self, context):
- self.reverse(context, make_ccw=(self.operation == 'INTERSECTION'))
-
-
-class archipack_floor_cutter_segment(ArchipackCutterPart, PropertyGroup):
- manipulators : CollectionProperty(type=archipack_manipulator)
- type : EnumProperty(
- name="Type",
- items=(
- ('DEFAULT', 'Side', 'Side with rake', 0),
- ('BOTTOM', 'Bottom', 'Bottom with gutter', 1),
- ('LINK', 'Side link', 'Side without decoration', 2),
- ('AXIS', 'Top', 'Top part with hip and beam', 3)
- # ('LINK_VALLEY', 'Side valley', 'Side with valley', 3),
- # ('LINK_HIP', 'Side hip', 'Side with hip', 4)
- ),
- default='DEFAULT',
- update=update_hole
- )
-
- def find_in_selection(self, context):
- selected = context.selected_objects[:]
- for o in selected:
- d = archipack_floor_cutter.datablock(o)
- if d:
- for part in d.parts:
- if part == self:
- return d
- return None
-
- def draw(self, layout, context, index):
- box = layout.box()
- box.label(text="Part:" + str(index + 1))
- # box.prop(self, "type", text=str(index + 1))
- box.prop(self, "length")
- box.prop(self, "a0")
-
-
-class archipack_floor_cutter(ArchipackCutter, ArchipackObject, Manipulable, PropertyGroup):
- parts : CollectionProperty(type=archipack_floor_cutter_segment)
-
- def update_points(self, context, o, pts, update_parent=False):
- """
- Create boundary from roof
- """
- self.auto_update = False
- self.from_points(pts)
- self.auto_update = True
- if update_parent:
- self.update_parent(context, o)
-
- def update_parent(self, context, o):
-
- d = archipack_floor.datablock(o.parent)
- if d is not None:
- o.parent.select_set(state=True)
- context.view_layer.objects.active = o.parent
- d.update(context)
- o.parent.select_set(state=False)
- context.view_layer.objects.active = o
-
-
-# ------------------------------------------------------------------
-# Define panel class to show object parameters in ui panel (N)
-# ------------------------------------------------------------------
-
-
-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
- scene = context.scene
- # retrieve datablock of your object
- props = archipack_floor.datablock(o)
- # manipulate
- layout.operator("archipack.floor_manipulate", icon="VIEW_PAN")
- layout.separator()
- 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='ADD')
- row.operator("archipack.floor_preset",
- text="",
- icon='REMOVE').remove_active = True
-
- box = layout.box()
- box.operator('archipack.floor_cutter').parent = o.name
-
- box = layout.box()
- box.label(text="From curve")
- box.prop_search(props, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
- if props.user_defined_path != "":
- box.prop(props, 'user_defined_resolution')
-
- box = layout.box()
- row = box.row()
- if props.parts_expand:
- row.prop(props, 'parts_expand', icon="TRIA_DOWN", text="Parts", emboss=False)
- box.prop(props, 'n_parts')
- # box.prop(prop, 'closed')
- for i, part in enumerate(props.parts):
- part.draw(context, layout, i)
- else:
- row.prop(props, 'parts_expand', icon="TRIA_RIGHT", text="Parts", emboss=False)
- layout.separator()
- box = layout.box()
- box.prop(props, 'pattern', text="")
- # thickness
- box.separator()
- box.prop(props, 'thickness')
- box.prop(props, 'vary_thickness', icon='RNDCURVE')
- if props.vary_thickness:
- box.prop(props, 'thickness_variance')
- box.separator()
- box.prop(props, 'solidify', icon='MOD_SOLIDIFY')
- box.separator()
- if props.pattern == 'boards':
- box.prop(props, 'board_length')
- box.prop(props, 'vary_length', icon='RNDCURVE')
- if props.vary_length:
- box.prop(props, 'length_variance')
- box.prop(props, 'max_boards')
- box.separator()
-
- # width
- box.prop(props, 'board_width')
- # vary width
- box.prop(props, 'vary_width', icon='RNDCURVE')
- if props.vary_width:
- box.prop(props, 'width_variance')
- box.separator()
- box.prop(props, 'length_spacing')
- box.prop(props, 'width_spacing')
-
- elif props.pattern in {'square_parquet', 'herringbone_parquet', 'herringbone'}:
- box.prop(props, 'short_board_length')
-
- if props.pattern != "square_parquet":
- box.prop(props, "board_width")
- box.prop(props, "spacing")
-
- if props.pattern == 'square_parquet':
- box.prop(props, 'boards_in_group')
- elif props.pattern in {'regular_tile', 'hopscotch', 'stepping_stone', 'hexagon', 'windmill'}:
- # width and length and mortar
- if props.pattern != "hexagon":
- box.prop(props, "tile_length")
- box.prop(props, "tile_width")
- box.prop(props, "spacing")
-
- if props.pattern in {"regular_tile", "boards"}:
- box.separator()
- box.prop(props, "random_offset", icon="RNDCURVE")
- if props.random_offset:
- box.prop(props, "offset_variance")
- else:
- box.prop(props, "offset")
-
- # grout
- box.separator()
- box.prop(props, 'add_grout', icon='MESH_GRID')
- if props.add_grout:
- box.prop(props, 'mortar_depth')
-
- # bevel
- box.separator()
- box.prop(props, 'bevel', icon='MOD_BEVEL')
- if props.bevel:
- box.prop(props, 'bevel_amount')
-
- box.separator()
- box.prop(props, "vary_materials", icon="MATERIAL")
- if props.vary_materials:
- box.prop(props, "matid")
-
-
-class ARCHIPACK_PT_floor_cutter(Panel):
- bl_idname = "ARCHIPACK_PT_floor_cutter"
- bl_label = "Floor Cutter"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_category = 'Archipack'
-
- @classmethod
- def poll(cls, context):
- return archipack_floor_cutter.filter(context.active_object)
-
- def draw(self, context):
- prop = archipack_floor_cutter.datablock(context.active_object)
- if prop is None:
- return
- layout = self.layout
- scene = context.scene
- box = layout.box()
- box.operator('archipack.floor_cutter_manipulate', icon='VIEW_PAN')
- box.prop(prop, 'operation', text="")
- box = layout.box()
- box.label(text="From curve")
- box.prop_search(prop, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
- if prop.user_defined_path != "":
- box.prop(prop, 'user_defined_resolution')
- # box.prop(prop, 'x_offset')
- # box.prop(prop, 'angle_limit')
- """
- box.prop_search(prop, "boundary", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
- """
- prop.draw(layout, context)
-
-
-# ------------------------------------------------------------------
-# Define operator class to create object
-# ------------------------------------------------------------------
-
-
-class ARCHIPACK_OT_floor(ArchipackCreateTool, Operator):
- bl_idname = "archipack.floor"
- bl_label = "Floor"
- bl_description = "Floor"
- bl_category = 'Archipack'
- bl_options = {'REGISTER', 'UNDO'}
-
- def create(self, context):
- """
- expose only basic params in operator
- use object property for other params
- """
- m = bpy.data.meshes.new("Floor")
- o = bpy.data.objects.new("Floor", m)
- d = m.archipack_floor.add()
- # make manipulators selectable
- d.manipulable_selectable = True
- angle_90 = pi / 2
- x, y, = 4, 4
- p = d.parts.add()
- p.a0 = - angle_90
- p.length = y
- p = d.parts.add()
- p.a0 = angle_90
- p.length = x
- p = d.parts.add()
- p.a0 = angle_90
- p.length = y
- p = d.parts.add()
- p.a0 = angle_90
- p.length = x
- d.n_parts = 4
- self.link_object_to_scene(context, o)
- o.select_set(state=True)
- context.view_layer.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 = context.scene.cursor.location
- # activate manipulators at creation time
- o.select_set(state=True)
- context.view_layer.objects.active = o
- self.manipulate()
- return {'FINISHED'}
- else:
- self.report({'WARNING'}, "Option only valid in Object mode")
- return {'CANCELLED'}
-
-
-class ARCHIPACK_OT_floor_from_curve(ArchipackCreateTool, Operator):
- bl_idname = "archipack.floor_from_curve"
- bl_label = "Floor curve"
- bl_description = "Create a floor 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'
- # -----------------------------------------------------
- # Draw (create UI interface)
- # -----------------------------------------------------
- # noinspection PyUnusedLocal
-
- def draw(self, context):
- layout = self.layout
- row = layout.row()
- row.label(text="Use Properties panel (N) to define parms", icon='INFO')
-
- def create(self, context):
- curve = context.active_object
- bpy.ops.archipack.floor(auto_manipulate=self.auto_manipulate, filepath=self.filepath)
- o = context.active_object
- d = archipack_floor.datablock(o)
- d.user_defined_path = curve.name
- 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_floor_from_wall(ArchipackCreateTool, Operator):
- bl_idname = "archipack.floor_from_wall"
- bl_label = "->Floor"
- bl_description = "Create a floor from a wall"
- bl_category = 'Archipack'
- bl_options = {'REGISTER', 'UNDO'}
-
- @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.floor(auto_manipulate=False, filepath=self.filepath)
- o = context.view_layer.objects.active
- d = archipack_floor.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
- o.matrix_world = wall.matrix_world.copy()
- return o
-
- # -----------------------------------------------------
- # Execute
- # -----------------------------------------------------
- def execute(self, context):
- if context.mode == "OBJECT":
- bpy.ops.object.select_all(action="DESELECT")
- o = self.create(context)
- o.select_set(state=True)
- context.view_layer.objects.active = o
- if self.auto_manipulate:
- bpy.ops.archipack.floor_manipulate('INVOKE_DEFAULT')
- return {'FINISHED'}
- else:
- self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
- return {'CANCELLED'}
-
-
-class ARCHIPACK_OT_floor_cutter(ArchipackCreateTool, Operator):
- bl_idname = "archipack.floor_cutter"
- bl_label = "Floor Cutter"
- bl_description = "Floor Cutter"
- bl_category = 'Archipack'
- bl_options = {'REGISTER', 'UNDO'}
-
- parent : StringProperty("")
-
- def create(self, context):
- m = bpy.data.meshes.new("Floor Cutter")
- o = bpy.data.objects.new("Floor Cutter", m)
- d = m.archipack_floor_cutter.add()
- parent = context.scene.objects.get(self.parent.strip())
- if parent is not None:
- o.parent = parent
- bbox = parent.bound_box
- angle_90 = pi / 2
- x0, y0, z = bbox[0]
- x1, y1, z = bbox[6]
- x = 0.2 * (x1 - x0)
- y = 0.2 * (y1 - y0)
- o.matrix_world = parent.matrix_world @ Matrix([
- [1, 0, 0, -3 * x],
- [0, 1, 0, 0],
- [0, 0, 1, 0],
- [0, 0, 0, 1]
- ])
- p = d.parts.add()
- p.a0 = - angle_90
- p.length = y
- p = d.parts.add()
- p.a0 = angle_90
- p.length = x
- p = d.parts.add()
- p.a0 = angle_90
- p.length = y
- d.n_parts = 3
- # d.close = True
- pd = archipack_floor.datablock(parent)
- pd.boundary = o.name
- else:
- o.location = context.scene.cursor.location
- # make manipulators selectable
- d.manipulable_selectable = True
- self.link_object_to_scene(context, o)
- o.select_set(state=True)
- context.view_layer.objects.active = o
- # self.add_material(o)
- self.load_preset(d)
- update_operation(d, context)
- return o
-
- # -----------------------------------------------------
- # Execute
- # -----------------------------------------------------
- def execute(self, context):
- if context.mode == "OBJECT":
- bpy.ops.object.select_all(action="DESELECT")
- o = self.create(context)
- o.select_set(state=True)
- context.view_layer.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_floor_preset_menu(PresetMenuOperator, Operator):
- bl_description = "Show Floor presets"
- 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', 'parts', 'n_parts', 'user_defined_path', 'user_defined_resolution']
-
-
-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'}
-
-
-class ARCHIPACK_OT_floor_cutter_manipulate(Operator):
- bl_idname = "archipack.floor_cutter_manipulate"
- bl_label = "Manipulate"
- bl_description = "Manipulate"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(self, context):
- return archipack_floor_cutter.filter(context.active_object)
-
- def invoke(self, context, event):
- d = archipack_floor_cutter.datablock(context.active_object)
- d.manipulable_invoke(context)
- return {'FINISHED'}
-
-
-def register():
- bpy.utils.register_class(archipack_floor_cutter_segment)
- bpy.utils.register_class(archipack_floor_cutter)
- Mesh.archipack_floor_cutter = CollectionProperty(type=archipack_floor_cutter)
- bpy.utils.register_class(ARCHIPACK_OT_floor_cutter)
- bpy.utils.register_class(ARCHIPACK_PT_floor_cutter)
- bpy.utils.register_class(ARCHIPACK_OT_floor_cutter_manipulate)
-
- bpy.utils.register_class(archipack_floor_part)
- 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)
- bpy.utils.register_class(ARCHIPACK_OT_floor_from_curve)
- bpy.utils.register_class(ARCHIPACK_OT_floor_from_wall)
-
-
-def unregister():
- bpy.utils.unregister_class(archipack_floor_cutter_segment)
- bpy.utils.unregister_class(archipack_floor_cutter)
- del Mesh.archipack_floor_cutter
- bpy.utils.unregister_class(ARCHIPACK_OT_floor_cutter)
- bpy.utils.unregister_class(ARCHIPACK_PT_floor_cutter)
- bpy.utils.unregister_class(ARCHIPACK_OT_floor_cutter_manipulate)
-
- bpy.utils.unregister_class(archipack_floor_part)
- 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)
- bpy.utils.unregister_class(ARCHIPACK_OT_floor_from_curve)
- bpy.utils.unregister_class(ARCHIPACK_OT_floor_from_wall)
diff --git a/archipack/archipack_gl.py b/archipack/archipack_gl.py
deleted file mode 100644
index 8f69bea8..00000000
--- a/archipack/archipack_gl.py
+++ /dev/null
@@ -1,1432 +0,0 @@
-# -*- 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 gpu
-from gpu_extras.batch import batch_for_shader
-import bpy
-import numpy as np
-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
- """
- text_size = 14
- 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)
- handle_colour_normal = (1.0, 1.0, 1.0, 1.0)
- handle_colour_hover = (1.0, 1.0, 0.0, 1.0)
- handle_colour_active = (1.0, 0.0, 0.0, 1.0)
- handle_colour_selected = (0.0, 0.0, 0.7, 1.0)
- handle_colour_inactive = (0.3, 0.3, 0.3, 1.0)
-
-
-def get_prefs(context):
- global __name__
- try:
- addon_name = __name__.split('.')[0]
- prefs = context.preferences.addons[addon_name].preferences
- except:
- prefs = DefaultColorScheme
- pass
- return prefs
-
-
-g_poly = None
-g_image = None
-g_line = None
-
-
-line_vertSrc = '''
-uniform mat4 ModelViewProjectionMatrix;
-
-in vec2 pos;
-
-void main()
-{
- gl_Position = ModelViewProjectionMatrix * vec4(pos, 0.0, 1.0);
-}
-
-'''
-
-line_fragSrc = '''
-uniform vec4 color;
-
-out vec4 fragColor;
-
-void main()
-{
- fragColor = color;
-}
-'''
-
-"""
-# TESTS
-_format = gpu.types.GPUVertFormat()
-_pos_id = _format.attr_add(
- id="pos",
- comp_type="F32",
- len=2,
- fetch_mode="FLOAT")
-coords = [(0,0), (200,100)]
-vbo = gpu.types.GPUVertBuf(len=len(coords), format=_format)
-"""
-
-
-class GPU_Line:
- def __init__(self):
- # never call this in -b mode
- if bpy.app.background:
- return
- self._format = gpu.types.GPUVertFormat()
- self._pos_id = self._format.attr_add(
- id="pos",
- comp_type="F32",
- len=2,
- fetch_mode="FLOAT")
- self.shader = gpu.types.GPUShader(line_vertSrc, line_fragSrc)
- self.unif_color = self.shader.uniform_from_name("color")
- self.color = np.array([0.0, 0.0, 0.0, 1.0], 'f')
-
- def batch_line_strip_create(self, coords):
- vbo = gpu.types.GPUVertBuf(len=len(coords), format=self._format)
- vbo.attr_fill(id=self._pos_id, data=coords)
- batch_lines = gpu.types.GPUBatch(type="LINE_STRIP", buf=vbo)
- batch_lines.program_set(self.shader)
- return batch_lines
-
- def draw(self, color, list_verts_co):
- if list_verts_co:
- batch = self.batch_line_strip_create(list_verts_co)
- self.color[0:4] = color[0:4]
- self.shader.uniform_vector_float(self.unif_color, self.color, 4)
- batch.draw()
- del batch
-
-
-class GPU_Poly:
-
- def __init__(self):
- if bpy.app.background:
- return
- self._format = gpu.types.GPUVertFormat()
- self._pos_id = self._format.attr_add(
- id="pos",
- comp_type="F32",
- len=2,
- fetch_mode="FLOAT")
- self.shader = gpu.types.GPUShader(line_vertSrc, line_fragSrc)
- self.unif_color = self.shader.uniform_from_name("color")
- self.color = np.array([0.0, 0.0, 0.0, 0.5], 'f')
-
- def batch_create(self, coords):
- vbo = gpu.types.GPUVertBuf(len=len(coords), format=self._format)
- vbo.attr_fill(id=self._pos_id, data=coords)
- batch = gpu.types.GPUBatch(type="TRI_FAN", buf=vbo)
- # batch.program_set(self.shader)
- # batch = batch_for_shader(self.shader, 'TRI_FAN', {"position": coords})
- return batch
-
- def draw(self, color, list_verts_co):
- if list_verts_co:
- self.shader.bind()
- batch = self.batch_create(list_verts_co)
- self.color[0:4] = color[0:4]
- self.shader.uniform_vector_float(self.unif_color, self.color, 4)
- batch.draw(self.shader)
-
-
-class GPU_Image:
- uvs = [(0, 0), (1, 0), (0, 1), (1, 1)]
- indices = [(0, 1, 2), (2, 1, 3)]
-
- def __init__(self):
- if bpy.app.background:
- return
- self._format = gpu.types.GPUVertFormat()
- self._pos_id = self._format.attr_add(
- id="pos",
- comp_type="F32",
- len=2,
- fetch_mode="FLOAT")
- self.shader = gpu.shader.from_builtin('2D_IMAGE')
-
- def batch_create(self, coords):
- batch = batch_for_shader(self.shader, 'TRIS',
- {"pos": coords,
- "texCoord": self.uvs},
- indices=self.indices)
- return batch
-
- def draw(self, texture_id, list_verts_co):
- if list_verts_co:
- batch = self.batch_create(list_verts_co)
-
- # in case someone disabled it before
- bgl.glEnable(bgl.GL_TEXTURE_2D)
-
- # bind texture to image unit 0
- bgl.glActiveTexture(bgl.GL_TEXTURE0)
- bgl.glBindTexture(bgl.GL_TEXTURE_2D, texture_id)
-
- self.shader.bind()
- # tell shader to use the image that is bound to image unit 0
- self.shader.uniform_int("image", 0)
- batch.draw(self.shader)
-
- bgl.glDisable(bgl.GL_TEXTURE_2D)
-
-
-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)):
-
- global g_poly, g_image, g_line
-
- if g_poly is None:
- g_poly = GPU_Poly()
-
- if g_image is None:
- g_image = GPU_Image()
-
- if g_line is None:
- g_line = GPU_Line()
-
- # 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 Vector(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, default=self.pos_2d)
- return Vector(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 Vector(round(co_2d.x * render_size[0]), round(co_2d.y * render_size[1]))
-
-
-class GlText(Gl):
-
- def __init__(self,
- d=3,
- label="",
- value=None,
- precision=4,
- unit_mode='AUTO',
- unit_type='SIZE',
- dimension=1,
- angle=0,
- font_size=None,
- 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 : ['ADAPTIVE','METER','CENTIMETER','MILIMETER','FEET','INCH','RADIANS','DEGREE']
- unit type to use to postfix values
- ADAPTIVE 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
- if font_size is None:
- prefs = get_prefs(bpy.context)
- self.font_size = prefs.text_size
- else:
- self.font_size = font_size
- self.angle = angle
- Gl.__init__(self, d)
- self.colour_inactive = colour
- # store text with units
- self._text = ""
- self.cbuff = bgl.Buffer(bgl.GL_FLOAT, 4)
-
- def text_size(self, context):
- """
- overall on-screen size in pixels
- """
- dpi, font_id = context.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 ""
- system = context.scene.unit_settings.system
- if self.unit_type == 'ANGLE':
- scale = 1
- mode = 'ADAPTIVE'
- else:
- scale = context.scene.unit_settings.scale_length
- mode = self.unit_mode
- if mode == 'AUTO':
- mode = context.scene.unit_settings.length_unit.upper()
-
- val = self.value * scale
-
- if mode == 'ADAPTIVE':
- if self.unit_type == 'ANGLE':
- mode = context.scene.unit_settings.system_rotation
- else:
- if system == "IMPERIAL":
- if round(val * (3.2808399 ** self.dimension), 2) >= 1.0:
- mode = 'FOOT'
- 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'
-
- # TODO: support for separate units (through 2.8 api)
- # convert values
- unit = ""
- 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 in {'FOOT', 'FEET'}:
- val *= (3.2808399 ** self.dimension)
- unit = "ft"
- elif mode == 'INCH':
- val *= (39.3700787 ** self.dimension)
- unit = "in"
- elif mode == 'RADIANS':
- unit = ""
- elif mode == 'DEGREES':
- val = self.value / pi * 180
- unit = "°"
- if system == 'IMPERIAL':
- if self.dimension == 2:
- unit = "sq " + unit
- elif self.dimension == 3:
- unit = "cu " + unit
- elif system == 'METRIC':
- if self.dimension == 2:
- unit += "\u00b2" # Superscript two
- elif self.dimension == 3:
- unit += "\u00b3" # Superscript three
-
- fmt = "%1." + str(self.precision) + "f"
- # remove trailing zeros
- res = fmt % val
- while res[-1] == '0':
- res = res[:-1]
-
- if res[-1] == ".":
- res = res + '0'
-
- return "{} {}".format(res, unit)
-
- 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):
-
- # print("draw_text %s %s" % (self.text, type(self).__name__))
- self.render = render
- p = self.position_2d_from_coord(context, self.pts[0], render)
-
- # dirty fast assignment
- dpi, font_id = context.preferences.system.dpi, 0
-
- # self.cbuff[0:4] = self.colour
-
- # bgl.glEnableClientState(bgl.GL_COLOR_ARRAY)
- # bgl.glColorPointer(4, bgl.GL_FLOAT, 0, self.cbuff)
- blf.color(0, *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, p.x, p.y, 0)
- blf.draw(font_id, self.text)
- if self.angle != 0:
- blf.disable(font_id, blf.ROTATION)
-
- # bgl.glDisableClientState(bgl.GL_COLOR_ARRAY)
-
-
-class GlBaseLine(Gl):
-
- def __init__(self,
- d=3,
- width=1,
- style=bgl.GL_LINE,
- closed=False,
- n_pts=2):
- Gl.__init__(self, d)
- # default line width
- self.width = width
- # default line style
- self.style = style
- # allow closed lines
- self.closed = closed
-
- self.n_pts = n_pts
-
- def draw(self, context, render=False):
- """
- render flag when rendering
- """
- self.render = render
- bgl.glEnable(bgl.GL_BLEND)
- bgl.glLineWidth(self.width)
- list_verts_co = [
- tuple(self.position_2d_from_coord(context, pt, render)[0:2])
- for i, pt in enumerate(self.pts)]
- if self.closed:
- list_verts_co.append(list_verts_co[0])
- g_line.draw(self.colour, list_verts_co)
- bgl.glLineWidth(1.0)
- bgl.glDisable(bgl.GL_BLEND)
-
-
-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()
- self.v = Vector()
- if z_axis is not None:
- self.z_axis = z_axis
- else:
- self.z_axis = Vector((0, 0, 1))
- GlBaseLine.__init__(self, d, n_pts=2)
-
- @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
- positioned 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.dot(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, n_pts=60)
-
- 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)))
- self.n_pts = n_pts
- 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.3, 0.3, 0.3, 1.0),
- d=3, n_pts=60):
- self.pts_3d = []
- Gl.__init__(self, d, colour)
-
- self.n_pts = min(n_pts, 60)
-
- 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
- """
- # return
-
- self.render = render
- if self.n_pts == 0:
- return
-
- pts = self.pts
-
- g_vertices = [
- tuple(self.position_2d_from_coord(context, pt, render)[0:2])
- for i, pt in enumerate(pts)]
- bgl.glEnable(bgl.GL_BLEND)
- g_poly.draw(self.colour, g_vertices)
- bgl.glDisable(bgl.GL_BLEND)
-
-
-class GlRect(GlPolygon):
- def __init__(self,
- colour=(0.0, 0.0, 0.0, 1.0),
- d=2):
- GlPolygon.__init__(self, colour, d, n_pts=4)
-
- @property
- def pts(self):
- return self.pts_2d
-
- def draw(self, context, render=False):
- pts = [
- self.position_2d_from_coord(context, pt, render)
- for pt in self.pts_3d
- ]
- x0, y0 = pts[0]
- x1, y1 = pts[1]
- self.pts_2d = [Vector((x, y)) for x, y in [(x0, y0), (x0, y1), (x1, y1), (x1, y0)]]
- GlPolygon.draw(self, context, render)
-
-
-class GlImage(Gl):
- def __init__(self,
- d=2,
- image=None):
- # GImage bindcode[0]
- self.image = image
- self.colour_inactive = (1, 1, 1, 1)
- Gl.__init__(self, d)
- self.pts_2d = [Vector((0, 0)), Vector((10, 10))]
- self.n_pts = 4
-
- 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
-
- p0 = self.pts[0]
- p1 = self.pts[1]
- coords = [(p0.x, p0.y), (p1.x, p0.y), (p0.x, p1.y), (p1.x, p1.y)]
- g_image.draw(self.image.bindcode, coords)
-
-
-class GlPolyline(GlBaseLine):
- def __init__(self, colour, d=3, n_pts=60):
- self.pts_3d = []
- GlBaseLine.__init__(self, d, n_pts=n_pts)
- 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, n_pts=4):
- """
- sensor_size : 2d size in pixels of sensor area
- size : 3d size of handle
- """
- GlPolygon.__init__(self, d=d, n_pts=n_pts)
- prefs = get_prefs(bpy.context)
- self.colour_active = prefs.handle_colour_active
- self.colour_hover = prefs.handle_colour_hover
- self.colour_normal = prefs.handle_colour_normal
- self.colour_selected = prefs.handle_colour_selected
- self.colour_inactive = prefs.handle_colour_inactive
- # variable symbol size in world coords
- self.size = size
- # constant symbol size in pixels
- self.symbol_size = sensor_size
- # scale factor for constant symbol pixel size
- self.scale = 1
- # sensor size in pixels
- 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 scale_factor(self, context, pts):
- """
- Compute screen scale factor for symbol
- given 2 points in symbol direction (eg start and end of arrow)
- """
- prefs = get_prefs(context)
- if not prefs.constant_handle_size:
- return 1
-
- p2d = []
- for pt in pts:
- p2d.append(self.position_2d_from_coord(context, pt))
- sx = [p.x for p in p2d]
- sy = [p.y for p in p2d]
- x = max(sx) - min(sx)
- y = max(sy) - min(sy)
- s = max(x, y)
- if s > 0:
- fac = self.symbol_size / s
- else:
- fac = 1
- return fac
-
- 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)
- x = self.up_axis * 0.5 * self.size
- y = self.c_axis * 0.5 * self.size
- pts = [self.sensor_center + v for v in [-x, x, -y, y]]
- self.scale = self.scale_factor(context, pts)
-
- 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, n_pts=4)
-
- @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.scale * self.size * scale
- y = c * self.scale * 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, n_pts=3)
-
- @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.scale * self.size * 4 * scale
- y = c * self.scale * self.size * scale
- return [self.pos_3d - x + y, self.pos_3d - x - y, self.pos_3d]
-
-
-class CruxHandle(GlHandle):
-
- def __init__(self, sensor_size, size, draggable=True, selectable=False):
- GlHandle.__init__(self, sensor_size, size, draggable, selectable, n_pts=0)
- self.branch_0 = GlPolygon((1, 1, 1, 1), d=3, n_pts=4)
- self.branch_1 = GlPolygon((1, 1, 1, 1), d=3, n_pts=4)
-
- def set_pos(self, context, pos_3d, direction, normal=Vector((0, 0, 1))):
- self.pos_3d = pos_3d
- self.pos_2d = self.position_2d_from_coord(context, self.sensor_center)
- o = self.pos_3d
- d = 0.5 * self.size
- x = direction.normalized()
- y = x.cross(normal)
-
- sx = x * 0.5 * self.size
- sy = y * 0.5 * self.size
- pts = [o + v for v in [-sx, sx, -sy, sy]]
- self.scale = self.scale_factor(context, pts)
-
- c = self.scale * d / 1.4242
- w = self.scale * self.size
- s = w - c
-
- xs = x * s
- xw = x * w
- ys = y * s
- yw = y * w
- p0 = o + xs + yw
- p1 = o + xw + ys
- p2 = o - xs - yw
- p3 = o - xw - ys
- p4 = o - xs + yw
- p5 = o + xw - ys
- p6 = o + xs - yw
- p7 = o - xw + ys
-
- self.branch_0.set_pos([p0, p1, p2, p3])
- self.branch_1.set_pos([p4, p5, p6, p7])
-
- @property
- def pts(self):
- return [self.pos_3d]
-
- 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_0.draw(context)
- self.branch_1.draw(context)
-
-
-class PlusHandle(GlHandle):
-
- def __init__(self, sensor_size, size, draggable=True, selectable=False):
- GlHandle.__init__(self, sensor_size, size, draggable, selectable, n_pts=0)
- self.branch_0 = GlPolygon((1, 1, 1, 1), d=3, n_pts=4)
- self.branch_1 = GlPolygon((1, 1, 1, 1), d=3, n_pts=4)
-
- def set_pos(self, context, pos_3d, direction, normal=Vector((0, 0, 1))):
- self.pos_3d = pos_3d
- self.pos_2d = self.position_2d_from_coord(context, self.sensor_center)
- o = self.pos_3d
- x = direction.normalized()
- y = x.cross(normal)
- sx = x * 0.5 * self.size
- sy = y * 0.5 * self.size
- pts = [o + v for v in [-sx, sx, -sy, sy]]
- self.scale = self.scale_factor(context, pts)
- w = self.scale * self.size
- s = self.scale * 0.25 * w
-
- xs = x * s
- xw = x * w
- ys = y * s
- yw = y * w
- p0 = o - xw + ys
- p1 = o + xw + ys
- p2 = o + xw - ys
- p3 = o - xw - ys
- p4 = o - xs + yw
- p5 = o + xs + yw
- p6 = o + xs - yw
- p7 = o - xs - yw
- self.branch_0.set_pos([p0, p1, p2, p3])
- self.branch_1.set_pos([p4, p5, p6, p7])
-
- @property
- def pts(self):
- return [self.pos_3d]
-
- 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_0.draw(context)
- self.branch_1.draw(context)
-
-
-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)
- ts = self.text_size(context)
- self.pos_2d = self.position_2d_from_coord(context, pos_3d)
- self.pos_2d.x += 0.5 * ts.x
- self.sensor_width, self.sensor_height = 0.5 * ts.x, ts.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, n_pts=4)
- self.image = GlImage(image=image)
- self.label = GlText(d=2, label=label.replace("_", " ").capitalize())
- self.frame = GlPolyline((1, 1, 1, 1), d=2, n_pts=4)
- 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.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:
- # system.window_draw_method in {'TRIPLE_BUFFER', 'ADAPTIVEMATIC'}):
- 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 = 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, n_pts=4)
- self.title_area = GlPolygon(colour=prefs.feedback_title_area, d=2, n_pts=4)
- 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 instructions(self, context, title, explanation, shortcuts):
- """
- position from bottom to top
- """
- prefs = 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, n_pts=2)
- self.line_x.style = style
- self.line_x.width = width
- self.line_x.colour_inactive = colour
- self.line_y = GlLine(d=2, n_pts=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
- p = Vector(location)
- x, y = p.x, p.y
- 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, n_pts=4)
- self.border.style = style
- self.border.width = width
- self.border.closed = True
- self.area = GlPolygon(areacolour, d=2, n_pts=4)
- 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):
- p = Vector(p0)
- x0, y0 = p.x, p.y
- p = Vector(p1)
- x1, y1 = p.x, p.y
- 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:
- # print("GlCursorArea.draw()")
- self.area.draw(context)
- self.border.draw(context)
diff --git a/archipack/archipack_handle.py b/archipack/archipack_handle.py
deleted file mode 100644
index 27da6b0b..00000000
--- a/archipack/archipack_handle.py
+++ /dev/null
@@ -1,185 +0,0 @@
-# -*- 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 .archipack_object import ArchipackCollectionManager
-
-
-def create_handle(context, parent, mesh):
- old = context.active_object
- handle = bpy.data.objects.new("Handle", mesh)
- handle['archipack_handle'] = True
- ArchipackCollectionManager.link_object_to_scene(context, handle)
- modif = handle.modifiers.new('Subsurf', 'SUBSURF')
- modif.render_levels = 4
- modif.levels = 1
- handle.parent = parent
- handle.matrix_world = parent.matrix_world.copy()
- context.view_layer.objects.active = handle
- m = handle.archipack_material.add()
- m.category = 'handle'
- m.material = 'DEFAULT'
- context.view_layer.objects.active = old
- 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
deleted file mode 100644
index 05458b9c..00000000
--- a/archipack/archipack_keymaps.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# -*- 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.window_manager.keyconfigs.active.preferences.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):
- res = False
- signature = (event.alt, event.ctrl, event.shift, event.type, event.value)
- for ev in against:
- # print ("check %s == %s" % (signature, ev))
- if ev['event'] == signature:
- # print("check True")
- res = True
- break
- return res
-
- def get_event(self, context, keyconfig, keymap_item):
- """
- Return simple keymaps event signature as array of 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 keymap.check(event, keymap.undo):
- """
- evs = [ev for k, ev in context.window_manager.keyconfigs[0].keymaps[keyconfig].keymap_items.items()
- if k == keymap_item]
- # ev = context.window_manager.keyconfigs[0].keymaps[keyconfig].keymap_items[keymap_item]
- res = []
- for ev in evs:
- key = ev.type
- if ev.ctrl:
- key += '+CTRL'
- if ev.alt:
- key += '+ALT'
- if ev.shift:
- key += '+SHIFT'
- res.append({'type': key, 'name': ev.name, 'event': (ev.alt, ev.ctrl, ev.shift, ev.type, ev.value)})
- return res
-
- def dump_keys(self, context, filename="/tmp/keymap.txt"):
- """
- Utility for developers :
- Dump all keymaps to a file
- filename : string a file path to dump keymaps
- """
- str = ""
- kms = context.window_manager.keyconfigs
- for name, km in kms.items():
- for key in km.keymaps.keys():
- str += "\n\n#--------------------------------\n{} - {}:\n#--------------------------------\n\n".format(name, 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
deleted file mode 100644
index f1d91cad..00000000
--- a/archipack/archipack_manipulator.py
+++ /dev/null
@@ -1,2415 +0,0 @@
-# -*- 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 already running
-# ones, and prevent select mode to start.
-#
-# TODO:
-# Other manipulators should use same technique to take
-# precedence over already 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.
-
-
-import logging
-logger = logging.getLogger("archipack")
-
-
-"""
- Change object location when moving 1 point
- When False, change data.origin instead
-"""
-USE_MOVE_OBJECT = True
-# 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
-
- def is_snapping(self, ctx):
- """
- Check if snap is active
- """
- return ctx.active_object and ctx.active_object.name.startswith("Archipack_")
-
- @property
- def dirty(self):
- """
- Check for manipulable validity
- to disable modal when required
- """
- o = bpy.data.objects.get(self.object_name)
- return (
- self.manipulable is None or
- o is None or
- # The object is not selected and snap is not active
- not (self.is_snapping(bpy.context) or o.select_get())
- )
-
- 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 already 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, DON'T 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 overridden
- 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 overridden
- 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 really
- # really really 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(None)
- 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:
- o = self.o
- # switch context so unselected object may be manipulable too
- old = context.object
- state = o.select_get()
- o.select_set(state=True)
- context.view_layer.objects.active = o
- if index > -1:
- getattr(data, attr)[index] = value
- else:
- setattr(data, attr, value)
- o.select_set(state=state)
- old.select_set(state=True)
- context.view_layer.objects.active = old
- except:
- pass
-
- def preTranslate(self, tM, vec):
- """
- return a preTranslated Matrix
- tM Matrix source
- vec Vector translation
- """
- return tM @ Matrix.Translation(vec)
-
- 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_set(state=True)
- context.view_layer.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_set(state=True)
- context.view_layer.objects.active = old
-
- def move(self, context, axis, value):
- """
- Move an object along local axis
- """
- self._move(self.o, axis, value)
-
-
-# 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_set(state=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
- logger.debug("WallSnapManipulator.sp_callback")
-
- if state == 'SUCCESS':
- o = self.o
- o.select_set(state=True)
- context.view_layer.objects.active = o
- # apply changes to wall
- d = self.datablock
- g = d.get_generator()
-
- # rotation relative to object
- rM = o.matrix_world.inverted().to_3x3()
- delta =rM @ sp.delta
- # 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.to_2d()
-
- # 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
- d.auto_update = False
- 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.a0
-
- 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.a0
- # move object when point 0
- if USE_MOVE_OBJECT:
- d.move_object(o, o.matrix_world.translation + sp.delta)
- # self.o.location += sp.delta
- # self.o.matrix_world.translation += sp.delta
- else:
- d.origin += 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)
- if hasattr(d, "relocate_childs"):
- d.relocate_childs(context, o, g)
- d.auto_update = True
- d.update(context)
-
- if state == 'CANCEL':
- self.mouse_release(context, event)
- logger.debug("WallSnapManipulator.sp_callback done")
-
- return
-
- def sp_draw(self, sp, context):
- # draw wall placeholders
- logger.debug("WallSnapManipulator.sp_draw")
-
- 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)
- logger.debug("WallSnapManipulator.sp_draw done")
-
- 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,
- 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_callback(self, context, event, state, sp):
- logger.debug("SizeManipulator.sp_callback")
- global gl_pts3d
-
- p0 = gl_pts3d[0].copy()
- p1 = gl_pts3d[1].copy()
-
- if state != 'CANCEL':
- p1 += sp.delta
-
- length = (p0 - p1).length
-
- if state != 'CANCEL' and event.alt:
- if event.shift:
- length = round(length, 2)
- else:
- length = round(length, 1)
-
- self.set_value(context, self.datablock, self.manipulator.prop1_name, length)
-
- if state != 'RUNNING':
- self.mouse_release(context, event)
-
- logger.debug("SizeManipulator.sp_callback done")
-
-
-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
- already 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
- already 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,
- 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,
- 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_callback(self, context, event, state, sp):
- logger.debug("SnapSizeLocationManipulator.sp_callback")
- global gl_pts3d
- p0 = gl_pts3d[0].copy()
- p1 = gl_pts3d[1].copy()
-
- if state != 'CANCEL':
- if self.handle_right.active:
- p1 += sp.delta
- else:
- p0 += sp.delta
-
- l0 = self.get_value(self.datablock, self.manipulator.prop1_name)
- length = (p0 - p1).length
-
- if state != 'CANCEL' and event.alt:
- if event.shift:
- length = round(length, 2)
- else:
- length = round(length, 1)
-
- dp = length - l0
-
- if self.handle_left.active:
- dp = -dp
- dl = 0.5 * dp
-
- # snap_helper = context.object
- 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)
-
- # 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)
- snap_helper.select_set(state=True)
-
- if state != 'RUNNING':
- self.mouse_release(context, event)
-
- logger.debug("SnapSizeLocationManipulator.sp_callback done")
-
-
-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,
- 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_callback(self, context, event, state, sp):
- logger.debug("DeltaLocationManipulator.sp_callback")
-
- if state == 'CANCEL':
- self.cancel(context, event)
- else:
- global gl_pts3d
- 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)
- snap_helper.select_set(state=True)
-
- if state == 'SUCCESS':
- self.mouse_release(context, event)
-
- logger.debug("DeltaLocationManipulator.sp_callback done")
-
- 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()
-
- # prevent ValueError in angle_signed
- if self.line_0.length == 0 or self.line_1.length == 0:
- return
-
- 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 {} already 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 positioned and a scaling vector
- # RADIUS = 1 absolute positioned (center) and 2 relatives (sides)
- # POLYGON = 2 absolute positioned 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: optional 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.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
- # Silently 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 is not None and 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"
- )
-
- # TODO: make simple instance vars
- 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
- manipulable_area = None
- manipulable_start_point = None
- manipulable_end_point = None
- 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
- """
-
- if self.keymap is None:
- self.keymap = Keymaps(context)
- self.manipulable_area = GlCursorArea()
- self.manipulable_start_point = Vector((0, 0))
- self.manipulable_end_point = Vector((0, 0))
-
- 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 is not None and 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 don't 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 overridden
- as it provide all needed
- functionality 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_material.py b/archipack/archipack_material.py
deleted file mode 100644
index 5ac29d99..00000000
--- a/archipack/archipack_material.py
+++ /dev/null
@@ -1,604 +0,0 @@
-# -*- 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
-import os
-# noinspection PyUnresolvedReferences
-from bpy.types import (
- Panel, PropertyGroup,
- Object, Operator
- )
-from bpy.props import (
- EnumProperty, CollectionProperty,
- StringProperty
- )
-
-
-setman = None
-libman = None
-
-
-class MatLib():
- """
- A material library .blend file
- Store material name
- Apply material to objects
- """
- def __init__(self, matlib_path, name):
- self.name = name
- try:
- self.path = os.path.join(matlib_path, name)
- except:
- pass
- self.materials = []
-
- def cleanup(self):
- self.materials.clear()
-
- def load_list(self, sort=False):
- """
- list material names
- """
- # print("MatLib.load_list(%s)" % (self.name))
- self.materials.clear()
- try:
- with bpy.data.libraries.load(self.path) as (data_from, data_to):
- for mat in data_from.materials:
- self.materials.append(mat)
- if sort:
- self.materials = list(sorted(self.materials))
- except:
- pass
-
- def has(self, name):
- return name in self.materials
-
- def load_mat(self, name, link):
- """
- Load a material from library
- """
- try:
- # print("MatLib.load_mat(%s) linked:%s" % (name, link))
- with bpy.data.libraries.load(self.path, link=link, relative=False) as (data_from, data_to):
- data_to.materials = [name]
- except:
- pass
-
- def get_mat(self, name, link):
- """
- apply a material by name to active_object
- into slot index
- lazy load material list on demand
- return material or None
- """
-
- # Lazy load material names
- if len(self.materials) < 1:
- self.load_list()
-
- # material belongs to this libraray
- if self.has(name):
-
- # load material
- self.load_mat(name, link)
-
- return bpy.data.materials.get(name)
-
- return None
-
-
-class MatlibsManager():
- """
- Manage multiple library
- Lazy load
- """
- def __init__(self):
- self.matlibs = []
-
- def cleanup(self):
- for lib in self.matlibs:
- lib.cleanup()
- self.matlibs.clear()
-
- def get_prefs(self, context):
- """
- let raise error if any
- """
- global __name__
- prefs = None
- # retrieve addon name from imports
- addon_name = __name__.split('.')[0]
- prefs = context.preferences.addons[addon_name].preferences
- return prefs
-
- @property
- def loaded_path(self):
- """
- Loaded matlibs filenames
- """
- return [lib.path for lib in self.matlibs]
-
- def from_data(self, name):
- return bpy.data.materials.get(name)
-
- def add_to_list(self, path):
- """
- Add material library to list
- only store name of lib
- reloading here doesn't make sense
- """
- loaded_path = self.loaded_path
-
- if os.path.exists(path):
- self.matlibs.extend(
- [
- MatLib(path, f) for f in os.listdir(path)
- if f.endswith(".blend") and os.path.join(path, f) not in loaded_path
- ]
- )
-
- def load_list(self, context):
- """
- list available library path
- """
- # default library
- dir_path = os.path.dirname(os.path.realpath(__file__))
- mat_path = os.path.join(dir_path, "materials")
- self.add_to_list(mat_path)
-
- # user def library path from addon prefs
- try:
- prefs = self.get_prefs(context)
- self.add_to_list(prefs.matlib_path)
- except:
- print("Archipack: Unable to load default material library, please check path in addon prefs")
- pass
-
- def apply(self, context, slot_index, name, link=False):
-
- o = context.active_object
- o.select_set(state=True)
-
- # material with same name exist in scene
- mat = self.from_data(name)
-
- # mat not in scene: try to load from lib
- if mat is None:
- # print("mat %s not found in scene, loading" % (name))
- # Lazy build matlibs list
- if len(self.matlibs) < 1:
- self.load_list(context)
-
- for lib in self.matlibs:
- mat = lib.get_mat(name, link)
- if mat is not None:
- break
-
- # nothing found, build a default mat
- if mat is None:
- mat = bpy.data.materials.new(name)
-
- if slot_index < len(o.material_slots):
- o.material_slots[slot_index].material = None
- o.material_slots[slot_index].material = mat
- o.active_material_index = slot_index
-
- if not link:
- # break link
- bpy.ops.object.make_local(type="SELECT_OBDATA_MATERIAL")
-
-
-class MaterialSetManager():
- """
- Manage material sets for objects
- Store material names for each set
- Lazy load at enumerate time
- """
- def __init__(self):
- """
- Store sets for each object type
- """
- self.objects = {}
- # hold reference of dynamic enumerator
- self.enums = {}
- self.default_enum = [('DEFAULT', 'Default', '', 0)]
-
-
- def get_filename(self, object_type):
-
- target_path = os.path.join("presets", "archipack_materials")
- target_path = bpy.utils.user_resource('SCRIPTS', path=target_path, create=True)
- return os.path.join(target_path, object_type) + '.txt'
-
- def cleanup(self):
- self.objects.clear()
- self.enums.clear()
-
- def register_set(self, object_type, set_name, materials_names):
-
- if object_type not in self.objects.keys():
- self.objects[object_type] = {}
-
- self.objects[object_type][set_name.upper()] = materials_names
-
- def load(self, object_type):
-
- filename = self.get_filename(object_type)
-
- # preset not found in user prefs, load from archipack's default
- if not os.path.exists(filename):
- rel_filepath = \
- os.path.sep + "presets" + os.path.sep + \
- "archipack_materials" + os.path.sep + object_type + '.txt'
-
- filename = os.path.dirname(os.path.realpath(__file__)) + rel_filepath
-
- # print("load filename %s" % filename)
-
- material_sets = {}
-
- # create file object, and set open mode
-
- try:
- with open(filename, 'r') as f:
- lines = f.readlines()
-
- for line in lines:
- s_key, mat_name = line.split("##|##")
- if str(s_key) not in material_sets.keys():
- material_sets[s_key] = []
- material_sets[s_key].append(mat_name.strip())
- except:
- print("Archipack: material preset for {} not found".format(object_type))
- pass
-
- s_keys = material_sets.keys()
- for s_key in s_keys:
- self.register_set(object_type, s_key, material_sets[s_key])
-
- self.make_enum(object_type, s_keys)
-
- def save(self, object_type):
- # always save in user prefs
- filename = self.get_filename(object_type)
- # print("filename:%s" % filename)
- o_dict = self.objects[object_type]
- lines = []
- s_keys = o_dict.keys()
- for s_key in s_keys:
- for mat in o_dict[s_key]:
- lines.append("{}##|##{}\n".format(s_key, mat))
- try:
- f = open(filename, 'w')
- f.writelines(lines)
- except:
- print("Archipack: An error occurred while saving {}".format(filename))
- pass
- finally:
- f.close()
-
- self.make_enum(object_type, s_keys)
-
- def add(self, context, set_name):
- o = context.active_object
- if "archipack_material" in o:
- object_type = o.archipack_material[0].category
- materials_names = [slot.name for slot in o.material_slots if slot.name != '']
- # print("%s " % materials_names)
- self.register_set(object_type, set_name, materials_names)
- self.save(object_type)
-
- def remove(self, context):
- o = context.active_object
- if "archipack_material" in o:
- d = o.archipack_material[0]
- object_type = d.category
- set_name = d.material
- s_keys = self.objects[object_type].keys()
- if set_name in s_keys:
- self.objects[object_type].pop(set_name)
- self.save(object_type)
- self.make_enum(object_type, s_keys)
-
- def get_materials(self, object_type, set_name):
- if object_type not in self.objects.keys():
- self.load(object_type)
- if object_type not in self.objects.keys():
- # print("Archipack: Unknown object type {}".format(object_type))
- return None
- if set_name not in self.objects[object_type].keys():
- # print("Archipack: set {} not found".format(set_name))
- return None
- return self.objects[object_type][set_name]
-
- def make_enum(self, object_type, s_keys):
- if len(s_keys) > 0:
- self.enums[object_type] = [(s.upper(), s.capitalize(), '', i) for i, s in enumerate(s_keys)]
-
- def get_enum(self, object_type):
-
- if object_type not in self.objects.keys():
- self.load(object_type)
-
- if object_type not in self.objects.keys():
- self.objects[object_type] = {}
-
- if object_type in self.enums:
- return self.enums[object_type]
-
- return self.default_enum
-
-
-def material_enum(self, context):
- global setman
- if setman is None:
- setman = MaterialSetManager()
- return setman.get_enum(self.category)
-
-
-def update(self, context):
- self.update(context)
-
-
-class archipack_material(PropertyGroup):
-
- category : StringProperty(
- name="Category",
- description="Archipack object name",
- default=""
- )
- material : EnumProperty(
- name="Material",
- description="Material Set name",
- items=material_enum,
- update=update
- )
-
- def apply_material(self, context, slot_index, name):
- global libman
-
- if libman is None:
- libman = MatlibsManager()
-
- libman.apply(context, slot_index, name, link=False)
-
- def update(self, context):
- global setman
-
- if setman is None:
- setman = MaterialSetManager()
-
- o = context.active_object
- sel = [
- c for c in o.children
- if 'archipack_material' in c and c.archipack_material[0].category == self.category]
-
- # handle wall's holes
- if o.data and "archipack_wall2" in o.data:
- if o.parent is not None:
- for child in o.parent.children:
- if ('archipack_hybridhole' in child or
- 'archipack_robusthole' in child or
- 'archipack_hole' in child):
- sel.append(child)
-
- sel.append(o)
-
- mats = setman.get_materials(self.category, self.material)
-
- if mats is None or len(mats) < 1:
- return False
-
- for ob in sel:
- context.view_layer.objects.active = ob
- for slot_index, mat_name in enumerate(mats):
- if slot_index >= len(ob.material_slots):
- bpy.ops.object.material_slot_add()
- self.apply_material(context, slot_index, mat_name)
-
- context.view_layer.objects.active = o
-
- return True
-
-
-class ARCHIPACK_PT_material(Panel):
- bl_idname = "ARCHIPACK_PT_material"
- bl_label = "Archipack Material"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_category = 'Archipack'
-
- @classmethod
- def poll(cls, context):
- return context.active_object is not None and 'archipack_material' in context.active_object
-
- def draw(self, context):
- layout = self.layout
- props = context.active_object.archipack_material[0]
- row = layout.row(align=True)
- row.prop(props, 'material', text="")
- row.operator('archipack.material_add', icon="ADD", text="")
- row.operator('archipack.material_remove', icon="REMOVE", text="")
-
-
-class ARCHIPACK_OT_material(Operator):
- bl_idname = "archipack.material"
- bl_label = "Material"
- bl_description = "Add archipack material"
- bl_options = {'REGISTER', 'UNDO'}
-
- category : StringProperty(
- name="Category",
- description="Archipack object name",
- default=""
- )
- material : StringProperty(
- name="Material",
- description="Material Set name",
- default=""
- )
-
- @classmethod
- def poll(cls, context):
- return context.active_object is not None
-
- def execute(self, context):
-
- o = context.active_object
-
- if 'archipack_material' in o:
- m = o.archipack_material[0]
- else:
- m = o.archipack_material.add()
-
- m.category = self.category
- try:
- m.material = self.material
- res = m.update(context)
- except:
- res = False
- pass
-
- if not res:
- print("Archipack: unable to add material {} for {}".format(self.material, self.category))
- # self.report({'WARNING'}, 'Material {} for {} not found'.format(self.material, self.category))
-
- return {'FINISHED'}
-
-
-class ARCHIPACK_OT_material_add(Operator):
- bl_idname = "archipack.material_add"
- bl_label = "Material"
- bl_description = "Add a set of archipack material"
- bl_options = {'REGISTER', 'UNDO'}
-
- material : StringProperty(
- name="Material",
- description="Material Set name",
- default=""
- )
-
- @classmethod
- def poll(cls, context):
- return context.active_object is not None
-
- def invoke(self, context, event):
- return context.window_manager.invoke_props_dialog(self)
-
- def execute(self, context):
-
- global setman
-
- if setman is None:
- setman = MaterialSetManager()
-
- setman.add(context, self.material)
-
- return {'FINISHED'}
-
-
-class ARCHIPACK_OT_material_remove(Operator):
- bl_idname = "archipack.material_remove"
- bl_label = "Material"
- bl_description = "Remove a set of archipack material"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(cls, context):
- return context.active_object is not None
-
- def execute(self, context):
-
- global setman
-
- if setman is None:
- setman = MaterialSetManager()
-
- setman.remove(context)
-
- return {'FINISHED'}
-
-
-class ARCHIPACK_OT_material_library(Operator):
- bl_idname = "archipack.material_library"
- bl_label = "Material Library"
- bl_description = "Add all archipack materials on a single object"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(cls, context):
- return context.active_object is not None
-
- def execute(self, context):
-
- global setman
-
- if setman is None:
- setman = MaterialSetManager()
-
- o = context.active_object
-
- if 'archipack_material' in o:
- m = o.archipack_material[0]
- else:
- m = o.archipack_material.add()
- o.data.materials.clear()
-
- for category in setman.objects.keys():
- prefix = category.capitalize() + "_"
- for part in setman.objects[category]["DEFAULT"]:
- name = prefix + part
- mat = m.get_material(name)
- o.data.materials.append(mat)
-
- return {'FINISHED'}
-
-
-def register():
- bpy.utils.register_class(archipack_material)
- Object.archipack_material = CollectionProperty(type=archipack_material)
- bpy.utils.register_class(ARCHIPACK_OT_material)
- bpy.utils.register_class(ARCHIPACK_OT_material_add)
- bpy.utils.register_class(ARCHIPACK_OT_material_remove)
- bpy.utils.register_class(ARCHIPACK_OT_material_library)
- bpy.utils.register_class(ARCHIPACK_PT_material)
-
-
-def unregister():
- global libman
- global setman
- if libman is not None:
- libman.cleanup()
- if setman is not None:
- setman.cleanup()
- bpy.utils.unregister_class(ARCHIPACK_PT_material)
- bpy.utils.unregister_class(ARCHIPACK_OT_material)
- bpy.utils.unregister_class(ARCHIPACK_OT_material_add)
- bpy.utils.unregister_class(ARCHIPACK_OT_material_remove)
- bpy.utils.unregister_class(ARCHIPACK_OT_material_library)
- del Object.archipack_material
- bpy.utils.unregister_class(archipack_material)
diff --git a/archipack/archipack_object.py b/archipack/archipack_object.py
deleted file mode 100644
index f513b506..00000000
--- a/archipack/archipack_object.py
+++ /dev/null
@@ -1,284 +0,0 @@
-# -*- 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
- )
-
-
-class ArchipackCollectionManager():
-
- @staticmethod
- def link_object_to_scene(context, o):
- coll_main = context.scene.collection.children.get("Archipack")
- if coll_main is None:
- coll_main = bpy.data.collections.new(name="Archipack")
- context.scene.collection.children.link(coll_main)
- coll_main.objects.link(o)
-
- @staticmethod
- def unlink_object_from_scene(o):
- for coll in o.users_collection:
- coll.objects.unlink(o)
-
-
-class ArchipackObject(ArchipackCollectionManager):
- """
- 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 otherwise
- 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 = 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_set(state=True)
- except:
- pass
- if self.previously_active is not None:
- self.previously_active.select_set(state=True)
- context.view_layer.objects.active = self.previously_active
- self.previously_selected = None
- self.previously_active = None
-
- def move_object(self, o, p):
- """
- When firstpoint is moving we must move object according
- p is new x, y location in world coordsys
- """
- p = Vector((p.x, p.y, o.matrix_world.translation.z))
- # p is in o coordsys
- if o.parent:
- o.location = p @ o.parent.matrix_world.inverted()
- o.matrix_world.translation = p
- else:
- o.location = p
- o.matrix_world.translation = p
-
-
-class ArchipackCreateTool(ArchipackCollectionManager):
- """
- 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
- d: archipack object datablock
- preset: full filename.py with path
- """
- d.auto_update = False
- fallback = True
- if self.filepath != "":
- try:
- bpy.ops.script.python_file_run(filepath=self.filepath)
- fallback = False
- except:
- pass
- if fallback:
- # fallback to load preset on background process
- try:
- with open(self.filepath) as f:
- lines = f.read()
- cmp = compile(lines, self.filepath, 'exec')
- exec(cmp)
- except:
- print("Archipack unable to load preset file : %s" % (self.filepath))
- pass
- d.auto_update = True
-
- def add_material(self, o, material='DEFAULT', category=None):
- # skip if preset already add material
- if "archipack_material" in o:
- return
- try:
- if category is None:
- category = self.archipack_category
- if bpy.ops.archipack.material.poll():
- bpy.ops.archipack.material(category=category, material=material)
- except:
- print("Archipack %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 ArchipackDrawTool(ArchipackCollectionManager):
- """
- 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(
- depsgraph=context.view_layer.depsgraph,
- origin=ray_origin_mouse,
- direction=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, d.width, y, 0 # d.z_offset
- return False, Matrix(), None, 0, Vector(), 0
diff --git a/archipack/archipack_preset.py b/archipack/archipack_preset.py
deleted file mode 100644
index 65ca7245..00000000
--- a/archipack/archipack_preset.py
+++ /dev/null
@@ -1,580 +0,0 @@
-# -*- 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
-import subprocess
-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
-)
-preset_paths = [os.path.join(path, "presets") for path in bpy.utils.script_paths()]
-addons_paths = [os.path.join(path, "addons") for path in bpy.utils.script_paths()]
-
-
-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
-
-
-class PresetMenuItem():
- def __init__(self, thumbsize, preset, image=None):
- name = bpy.path.display_name_from_filepath(preset)
- self.preset = preset
- self.image = image
- self.image.gl_load()
- self.handle = ThumbHandle(thumbsize, name, self.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 cleanup(self):
- if self.image:
- self.image.gl_free()
- # bpy.data.images.remove(self.image)
-
- 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 item in self.menuItems:
- item.cleanup()
- 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 and bpy.data.images[img_idx].filepath_raw == filepath:
- 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')
- if hasattr(image, "colorspace_settings"):
- image.colorspace_settings.name = 'Raw'
- 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)
- y_max -= 20
- 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
- o = context.active_object
- if o.data and self.preset_subdir in o.data:
- d = getattr(o.data, self.preset_subdir)[0]
- elif self.preset_subdir in o:
- d = getattr(o, self.preset_subdir)[0]
- 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)
- ret.sort()
- return ret
-
- @property
- def preset_defines(self):
- o = bpy.context.active_object
- m = o.archipack_material[0]
- return [
- "d = bpy.context.active_object.data." + self.preset_subdir + "[0]",
- "bpy.ops.archipack.material(category='" + m.category + "', material='" + m.material + "')"
- ]
-
- def pre_cb(self, context):
- return
-
- def remove(self, context, filepath):
- # remove preset
- os.remove(filepath)
- # remove thumb
- os.remove(filepath[:-3] + ".png")
-
- def background_render(self, context, cls, preset):
- generator = os.path.dirname(os.path.realpath(__file__)) + os.path.sep + "archipack_thumbs.py"
- addon_name = __name__.split('.')[0]
- matlib_path = context.preferences.addons[addon_name].preferences.matlib_path
- # Run external instance of blender like the original thumbnail generator.
- cmd = [
- bpy.app.binary_path,
- "--background",
- "--factory-startup",
- "-noaudio",
- # "--addons", addon_name,
- "--python", generator,
- "--",
- "addon:" + addon_name,
- "matlib:" + matlib_path,
- "cls:" + cls,
- "preset:" + preset
- ]
-
- subprocess.Popen(cmd)
-
- 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', path=target_path, create=True)
-
- preset = os.path.join(target_path, filename) + ".py"
- cls = self.preset_subdir[10:]
- # print("post cb cls:%s preset:%s" % (cls, preset))
- self.background_render(context, cls, preset)
-
- return
diff --git a/archipack/archipack_progressbar.py b/archipack/archipack_progressbar.py
deleted file mode 100644
index 35ed166d..00000000
--- a/archipack/archipack_progressbar.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# -*- 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 reportpanel.py by Michel Anders
-# ----------------------------------------------------------
-import bpy
-from bpy.props import FloatProperty, StringProperty
-from bpy.types import Scene
-from time import time
-
-
-last_update = 0
-info_header_draw = None
-
-
-def update(self, context):
- global last_update
- if (context.window is not None and
- context.window.screen is not None and
- context.window.screen.areas is not None):
- areas = context.window.screen.areas
- for area in areas:
- if area.type == 'INFO':
- area.tag_redraw()
- if time() - last_update > 0.1:
- bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
- last_update = time()
-
-
-def register():
- Scene.archipack_progress = FloatProperty(
- options={'SKIP_SAVE'},
- default=-1,
- subtype='PERCENTAGE',
- precision=1,
- min=-1,
- soft_min=0,
- soft_max=100,
- max=101,
- update=update)
-
- Scene.archipack_progress_text = StringProperty(
- options={'SKIP_SAVE'},
- default="Progress",
- update=update)
-
- global info_header_draw
- info_header_draw = bpy.types.INFO_HT_header.draw
-
- def info_draw(self, context):
- global info_header_draw
- info_header_draw(self, context)
- if (context.scene.archipack_progress > -1 and
- context.scene.archipack_progress < 101):
- self.layout.separator()
- text = context.scene.archipack_progress_text
- self.layout.prop(context.scene,
- "archipack_progress",
- text=text,
- slider=True)
-
- bpy.types.INFO_HT_header.draw = info_draw
-
-
-def unregister():
- del Scene.archipack_progress
- del Scene.archipack_progress_text
- global info_header_draw
- bpy.types.INFO_HT_header.draw = info_header_draw
diff --git a/archipack/archipack_reference_point.py b/archipack/archipack_reference_point.py
deleted file mode 100644
index 5548511c..00000000
--- a/archipack/archipack_reference_point.py
+++ /dev/null
@@ -1,480 +0,0 @@
-# -*- 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,
- EnumProperty
- )
-from mathutils import Vector
-from .bmesh_utils import BmeshEdit as bmed
-from .archipack_object import ArchipackCollectionManager
-
-
-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)
- symbol_type : EnumProperty(
- name="Symbol type",
- default='WALL',
- items=(
- ('WALL', 'Wall', '', 0),
- ('ROOF', 'Roof', '', 1)),
- update=update)
-
- @classmethod
- def filter(cls, o):
- """
- Filter object with this class in data
- return
- True when object contains this datablock
- False otherwise
- 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
-
- if self.symbol_type == 'WALL':
-
- 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)]
-
- elif self.symbol_type == 'ROOF':
-
- 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.05, 0.05, 0.673), (-0.05, 0.674, -0.05), (-0.05, -0.05, 0.673), (-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.05, 0.05, 0.673), (0.05, -0.05, 0.673), (0.05, 0.674, 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),
- (0.674, -0.05, 0.05), (0.108, 0.0, 0.216), (0.09, 0.0, 0.261), (0.001, 0.107, 0.216),
- (0.001, -0.107, 0.216), (-0.107, 0.0, 0.216), (0.0, -0.089, 0.261), (0.0, 0.089, 0.261),
- (-0.089, 0.0, 0.261), (0.0, 0.042, 0.694), (-0.042, 0.0, 0.694), (0.0, -0.042, 0.694),
- (0.042, 0.0, 0.694)]]
-
- edges = [
- (1, 0), (0, 9), (10, 1), (3, 1), (11, 3), (2, 3), (12, 2), (0, 2),
- (17, 22), (17, 19), (18, 23), (13, 14), (14, 15), (15, 16), (16, 13),
- (15, 28), (17, 20), (24, 21), (18, 24), (14, 26), (13, 25), (16, 27),
- (45, 29), (46, 29), (47, 30), (48, 30), (23, 21), (23, 31), (24, 31),
- (32, 34), (35, 33), (32, 35), (34, 33), (34, 36), (35, 36), (28, 37),
- (6, 38), (26, 37), (26, 39), (25, 39), (5, 43), (5, 44), (25, 41),
- (27, 41), (7, 44), (8, 42), (28, 40), (27, 40), (20, 45), (22, 46),
- (22, 47), (20, 48), (18, 19), (18, 21), (18, 31), (17, 30), (17, 29),
- (32, 19), (32, 33), (32, 36), (4, 6), (4, 7), (4, 8), (4, 5), (8, 38),
- (6, 43), (7, 42), (9, 10), (10, 11), (11, 12), (12, 9)]
-
- 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')
- layout.separator()
- layout.operator('archipack.apply_holes')
-
-
-class ARCHIPACK_OT_reference_point(ArchipackCollectionManager, 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))
- )
- symbol_type : EnumProperty(
- name="Symbol type",
- default='WALL',
- items=(
- ('WALL', 'Wall', '', 0),
- ('ROOF', 'Roof', '', 1))
- )
-
- @classmethod
- def poll(cls, context):
- return context.active_object is not None
-
- def draw(self, context):
- layout = self.layout
- row = layout.row()
- row.label(text="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))
- self.link_object_to_scene(context, o)
- d = o.archipack_reference_point.add()
- d.location_2d = Vector((x, y, 0))
- d.location_3d = self.location_3d
- d.symbol_type = self.symbol_type
- o.select_set(state=True)
- context.view_layer.objects.active = o
- d.update(context)
- return o
-
- def execute(self, context):
- if context.mode == "OBJECT":
- o = self.create(context)
- o.select_set(state=True)
- context.view_layer.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_apply_holes(Operator):
- bl_idname = "archipack.apply_holes"
- bl_label = "Apply holes"
- bl_description = "Apply and remove holes from scene"
- bl_category = 'Archipack'
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(cls, context):
- return archipack_reference_point.filter(context.active_object)
-
- def apply_boolean(self, context, o):
- # mods = [m for m in o.modifiers if m.type == 'BOOLEAN']
- ctx = bpy.context.copy()
- ctx['object'] = o
- for mod in o.modifiers[:]:
- ctx['modifier'] = mod
- try:
- bpy.ops.object.modifier_apply(ctx, modifier=mod.name)
- except:
- pass
-
- def execute(self, context):
- if context.mode == "OBJECT":
- o = context.active_object
- to_remove = []
-
- for c in o.children:
- if 'archipack_hybridhole' in c:
- self.apply_boolean(context, c)
- to_remove.append(c)
-
- for c in o.children:
- if c.data is not None and "archipack_wall2" in c.data:
- self.apply_boolean(context, c)
-
- for c in o.children:
- if c.data is not None and (
- "archipack_window" in c.data or
- "archipack_door" in c.data):
- for h in c.children:
- if "archipack_hole" in h:
- to_remove.append(h)
-
- bpy.ops.object.select_all(action="DESELECT")
- for r in to_remove:
- r.hide_select = False
- r.select_set(state=True)
- context.view_layer.objects.active = r
- bpy.ops.object.delete(use_global=False)
-
- o.select_set(state=True)
- context.view_layer.objects.active = o
-
- 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_set(state=True)
- bpy.ops.archipack.parent_to_reference()
- self.unlink_object_from_scene(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)
- bpy.utils.register_class(ARCHIPACK_OT_apply_holes)
-
-
-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)
- bpy.utils.unregister_class(ARCHIPACK_OT_apply_holes)
diff --git a/archipack/archipack_rendering.py b/archipack/archipack_rendering.py
deleted file mode 100644
index f8bb07f9..00000000
--- a/archipack/archipack_rendering.py
+++ /dev/null
@@ -1,194 +0,0 @@
-# -*- 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 shutil import copyfile
-from os import path, listdir
-import subprocess
-# noinspection PyUnresolvedReferences
-import bpy_extras.image_utils as img_utils
-# noinspection PyUnresolvedReferences
-from math import ceil
-from bpy.types import Operator
-# from bl_ui import properties_render
-
-
-class ARCHIPACK_OT_render_thumbs(Operator):
- bl_idname = "archipack.render_thumbs"
- bl_label = "Render presets thumbs"
- bl_description = "Setup default presets and update thumbs"
- bl_options = {'REGISTER', 'INTERNAL'}
-
- @classmethod
- def poll(cls, context):
- # Ensure CYCLES engine is available
- # hasattr(context.scene, 'cycles')
- return context.scene
-
- def background_render(self, context, cls, preset):
- generator = path.dirname(path.realpath(__file__)) + path.sep + "archipack_thumbs.py"
- addon_name = __name__.split('.')[0]
- matlib_path = context.preferences.addons[addon_name].preferences.matlib_path
- # Run external instance of blender like the original thumbnail generator.
- cmd = [
- bpy.app.binary_path,
- "--background",
- "--factory-startup",
- "-noaudio",
- # "--addons", addon_name,
- "--python", generator,
- "--",
- "addon:" + addon_name,
- "matlib:" + matlib_path,
- "cls:" + cls,
- "preset:" + preset
- ]
-
- # print(" ".join(cmd))
-
- popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
- for stdout_line in iter(popen.stdout.readline, ""):
- yield stdout_line
- popen.stdout.close()
- popen.wait()
-
- def copy_to_user_path(self, category):
- """
- Copy factory presets to writeable presets folder
- Two cases here:
- 1 there is not presets thumbs found (official version)
- 2 thumbs already are there (unofficial)
- """
- file_list = []
- # load default presets
- dir_path = path.dirname(path.realpath(__file__))
- sub_path = "presets" + path.sep + category
- source_path = path.join(dir_path, sub_path)
- if path.exists(source_path):
- file_list.extend([f
- for f in listdir(source_path)
- if (f.endswith('.py') or f.endswith('.txt')) and
- not f.startswith('.')])
-
- target_path = path.join("presets", category)
- presets_path = bpy.utils.user_resource('SCRIPTS', path=target_path, create=True)
- # files from factory not found in user doesn't require a recompute
- skipfiles = []
- for f in file_list:
- # copy python/txt preset
- if not path.exists(presets_path + path.sep + f):
- copyfile(source_path + path.sep + f, presets_path + path.sep + f)
-
- # skip txt files (material presets)
- if f.endswith(".txt"):
- skipfiles.append(f)
-
- # when thumbs already are in factory folder but not found in user one
- # simply copy them, and add preset to skip list
- thumb_filename = f[:-3] + ".png"
- if path.exists(source_path + path.sep + thumb_filename):
- if not path.exists(presets_path + path.sep + thumb_filename):
- copyfile(source_path + path.sep + thumb_filename, presets_path + path.sep + thumb_filename)
- skipfiles.append(f)
-
- return skipfiles
-
- def scan_files(self, category):
- file_list = []
-
- # copy from factory to user writeable folder
- skipfiles = self.copy_to_user_path(category)
-
- # load user def presets
- preset_paths = bpy.utils.script_paths(subdir="presets")
- for preset in preset_paths:
- presets_path = path.join(preset, category)
- if path.exists(presets_path):
- file_list += [presets_path + path.sep + f[:-3]
- for f in listdir(presets_path)
- if f.endswith('.py') and
- not f.startswith('.') and
- f not in skipfiles]
-
- file_list.sort()
- return file_list
-
- def rebuild_thumbs(self, context):
- file_list = []
- dir_path = path.dirname(path.realpath(__file__))
- sub_path = "presets"
- presets_path = path.join(dir_path, sub_path)
- # print(presets_path)
- if path.exists(presets_path):
- dirs = listdir(presets_path)
- for dir in dirs:
- abs_dir = path.join(presets_path, dir)
- if path.isdir(abs_dir):
- files = self.scan_files(dir)
- file_list.extend([(dir, file) for file in files])
-
- ttl = len(file_list)
- for i, preset in enumerate(file_list):
- dir, file = preset
- cls = dir[10:]
- # context.scene.archipack_progress = (100 * i / ttl)
- log_all = False
- for l in self.background_render(context, cls, file + ".py"):
- if "[log]" in l:
- print(l[5:].strip())
- elif "blender.crash" in l:
- print("Unexpected error")
- log_all = True
- if log_all:
- print(l.strip())
-
- def invoke(self, context, event):
- addon_name = __name__.split('.')[0]
- matlib_path = context.preferences.addons[addon_name].preferences.matlib_path
-
- if matlib_path == '':
- self.report({'WARNING'}, "You should setup a default material library path in addon preferences")
- return context.window_manager.invoke_confirm(self, event)
-
- def execute(self, context):
- # context.scene.archipack_progress_text = 'Generating thumbs'
- # context.scene.archipack_progress = 0
- self.rebuild_thumbs(context)
- # context.scene.archipack_progress = -1
- return {'FINISHED'}
-
-
-def register():
- bpy.utils.register_class(ARCHIPACK_OT_render_thumbs)
-
-
-def unregister():
- bpy.utils.unregister_class(ARCHIPACK_OT_render_thumbs)
diff --git a/archipack/archipack_roof.py b/archipack/archipack_roof.py
deleted file mode 100644
index 8a9c8341..00000000
--- a/archipack/archipack_roof.py
+++ /dev/null
@@ -1,5413 +0,0 @@
-# -*- 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
-import time
-# noinspection PyUnresolvedReferences
-from bpy.types import Operator, PropertyGroup, Mesh, Panel
-from bpy.props import (
- FloatProperty, BoolProperty, IntProperty,
- StringProperty, EnumProperty,
- CollectionProperty
- )
-from .bmesh_utils import BmeshEdit as bmed
-from random import randint
-import bmesh
-from mathutils import Vector, Matrix
-from math import sin, cos, pi, atan2, sqrt, tan
-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
-from .archipack_cutter import (
- CutAblePolygon, CutAbleGenerator,
- ArchipackCutter,
- ArchipackCutterPart
- )
-
-
-class Roof():
-
- def __init__(self):
- self.angle_0 = 0
- self.v0_idx = 0
- self.v1_idx = 0
- self.constraint_type = None
- self.slope_left = 1
- self.slope_right = 1
- self.width_left = 1
- self.width_right = 1
- self.auto_left = 'AUTO'
- self.auto_right = 'AUTO'
- self.type = 'SIDE'
- # force hip or valley
- self.enforce_part = 'AUTO'
- self.triangular_end = False
- # seg is part of hole
- self.is_hole = False
-
- def copy_params(self, s):
- s.angle_0 = self.angle_0
- s.v0_idx = self.v0_idx
- s.v1_idx = self.v1_idx
- s.constraint_type = self.constraint_type
- s.slope_left = self.slope_left
- s.slope_right = self.slope_right
- s.width_left = self.width_left
- s.width_right = self.width_right
- s.auto_left = self.auto_left
- s.auto_right = self.auto_right
- s.type = self.type
- s.enforce_part = self.enforce_part
- s.triangular_end = self.triangular_end
- # segment is part of hole / slice
- s.is_hole = self.is_hole
-
- @property
- def copy(self):
- s = StraightRoof(self.p.copy(), self.v.copy())
- self.copy_params(s)
- return s
-
- def straight(self, length, t=1):
- s = self.copy
- s.p = self.lerp(t)
- s.v = self.v.normalized() * length
- return s
-
- def set_offset(self, offset, last=None):
- """
- Offset line and compute intersection point
- between segments
- """
- self.line = self.make_offset(offset, last)
-
- def offset(self, offset):
- o = self.copy
- o.p += offset * self.cross_z.normalized()
- return o
-
- @property
- def oposite(self):
- o = self.copy
- o.p += o.v
- o.v = -o.v
- return o
-
- @property
- def t_diff(self):
- return self.t_end - self.t_start
-
- def straight_roof(self, a0, length):
- s = self.straight(length).rotate(a0)
- r = StraightRoof(s.p, s.v)
- r.angle_0 = a0
- return r
-
- def curved_roof(self, a0, da, radius):
- n = self.normal(1).rotate(a0).scale(radius)
- if da < 0:
- n.v = -n.v
- c = n.p - n.v
- r = CurvedRoof(c, radius, n.angle, da)
- r.angle_0 = a0
- return r
-
-
-class StraightRoof(Roof, Line):
- def __str__(self):
- return "p0:{} p1:{}".format(self.p0, self.p1)
-
- def __init__(self, p, v):
- Line.__init__(self, p, v)
- Roof.__init__(self)
-
-
-class CurvedRoof(Roof, 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):
- Arc.__init__(self, c, radius, a0, da)
- Roof.__init__(self)
-
-
-class RoofSegment():
- """
- Roof part with 2 polygons
- and "axis" StraightRoof segment
- """
- def __init__(self, seg, left, right):
- self.seg = seg
- self.left = left
- self.right = right
- self.a0 = 0
- self.reversed = False
-
-
-class RoofAxisNode():
- """
- Connection between parts
- for radial analysis
- """
- def __init__(self):
- # axis segments
- self.segs = []
- self.root = None
- self.center = 0
- # store count of horizontal segs
- self.n_horizontal = 0
- # store count of slopes segs
- self.n_slope = 0
-
- @property
- def count(self):
- return len(self.segs)
-
- @property
- def last(self):
- """
- last segments in this node
- """
- return self.segs[-1]
-
- def left(self, index):
- if index + 1 >= self.count:
- return self.segs[0]
- return self.segs[index + 1]
-
- def right(self, index):
- return self.segs[index - 1]
-
- def add(self, a0, reversed, seg, left, right):
-
- if seg.constraint_type == 'HORIZONTAL':
- self.n_horizontal += 1
- elif seg.constraint_type == 'SLOPE':
- self.n_slope += 1
-
- s = RoofSegment(seg, left, right)
- s.a0 = a0
- s.reversed = reversed
- if reversed:
- self.root = s
- self.segs.append(s)
-
- def update_center(self):
- for i, s in enumerate(self.segs):
- if s is self.root:
- self.center = i
- return
-
- # sort tree segments by angle
- def partition(self, array, begin, end):
- pivot = begin
- for i in range(begin + 1, end + 1):
- if array[i].a0 < array[begin].a0:
- pivot += 1
- array[i], array[pivot] = array[pivot], array[i]
- array[pivot], array[begin] = array[begin], array[pivot]
- return pivot
-
- def sort(self):
- 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)
-
- end = len(self.segs) - 1
- _quicksort(self.segs, 0, end)
-
- # index of root in segs array
- self.update_center()
-
-
-class RoofPolygon(CutAblePolygon):
- """
- ccw roof pitch boundary
- closed by explicit segment
- handle triangular shape with zero axis length
-
- mov <_________________
- | /\
- | | rot
- | | left last <> next
- \/_____axis_______>|
-
- node <_____axis________ next
- | /\
- | | rot
- | | right last <> next
- mov \/________________>|
- side angle
- """
- def __init__(self, axis, side, fake_axis=None):
- """
- Create a default rectangle
- axis from node to next
- slope float -z for 1 in side direction
- side in ['LEFT', 'RIGHT'] in axis direction
-
- NOTE:
- when axis length is null (eg: triangular shape)
- use "fake_axis" with a 1 length to handle
- distance from segment
- """
- if side == 'LEFT':
- # slope
- self.slope = axis.slope_left
- # width
- self.width = axis.width_left
- # constraint width
- self.auto_mode = axis.auto_left
- else:
- # slope
- self.slope = axis.slope_right
- # width
- self.width = axis.width_right
- # constraint width
- self.auto_mode = axis.auto_right
-
- self.side = side
- # backward deps
- self.backward = False
- # pointers to neighbors along axis
- self.last = None
- self.next = None
- self.other_side = None
-
- # axis segment
- if side == 'RIGHT':
- self.axis = axis.oposite
- else:
- self.axis = axis
-
- self.fake_axis = None
-
- # _axis is either a fake one or real one
- # to prevent further check
- if fake_axis is None:
- self._axis = self.axis
- self.fake_axis = self.axis
- self.next_cross = axis
- self.last_cross = axis
- else:
- if side == 'RIGHT':
- self.fake_axis = fake_axis.oposite
- else:
- self.fake_axis = fake_axis
- self._axis = self.fake_axis
-
- # unit vector perpendicular to axis
- # looking at outside part
- v = self.fake_axis.sized_normal(0, -1)
- self.cross = v
- self.next_cross = v
- self.last_cross = v
-
- self.convex = True
- # segments from axis end in ccw order
- # closed by explicit segment
- self.segs = []
- # holes
- self.holes = []
-
- # Triangular ends
- self.node_tri = False
- self.next_tri = False
- self.is_tri = False
-
- # sizes
- self.tmin = 0
- self.tmax = 1
- self.dt = 1
- self.ysize = 0
- self.xsize = 0
- self.vx = Vector()
- self.vy = Vector()
- self.vz = Vector()
-
- def move_node(self, p):
- """
- Move slope point in node side
- """
- if self.side == 'LEFT':
- self.segs[-1].p0 = p
- self.segs[2].p1 = p
- else:
- self.segs[2].p0 = p
- self.segs[1].p1 = p
-
- def move_next(self, p):
- """
- Move slope point in next side
- """
- if self.side == 'LEFT':
- self.segs[2].p0 = p
- self.segs[1].p1 = p
- else:
- self.segs[-1].p0 = p
- self.segs[2].p1 = p
-
- def node_link(self, da):
- angle_90 = round(pi / 2, 4)
- if self.side == 'LEFT':
- idx = -1
- else:
- idx = 1
- da = abs(round(da, 4))
- type = "LINK"
- if da < angle_90:
- type += "_VALLEY"
- elif da > angle_90:
- type += "_HIP"
- self.segs[idx].type = type
-
- def next_link(self, da):
- angle_90 = round(pi / 2, 4)
- if self.side == 'LEFT':
- idx = 1
- else:
- idx = -1
- da = abs(round(da, 4))
- type = "LINK"
- if da < angle_90:
- type += "_VALLEY"
- elif da > angle_90:
- type += "_HIP"
- self.segs[idx].type = type
-
- def bind(self, last, ccw=False):
- """
- always in axis real direction
- """
- # backward dependency relative to axis
- if last.backward:
- self.backward = self.side == last.side
-
- if self.side == last.side:
- last.next_cross = self.cross
- else:
- last.last_cross = self.cross
-
- self.last_cross = last.cross
-
- # axis of last / next segments
- if self.backward:
- self.next = last
- last.last = self
- else:
- self.last = last
- last.next = self
-
- # width auto
- if self.auto_mode == 'AUTO':
- self.width = last.width
- self.slope = last.slope
- elif self.auto_mode == 'WIDTH' and self.width != 0:
- self.slope = last.slope * last.width / self.width
- elif self.auto_mode == 'SLOPE' and self.slope != 0:
- self.width = last.width * last.slope / self.slope
-
- self.make_segments()
- last.make_segments()
-
- res, p, t = self.segs[2].intersect(last.segs[2])
-
- if res:
- # dont move anything when no intersection found
- # aka when delta angle == 0
- self.move_node(p)
- if self.side != last.side:
- last.move_node(p)
- else:
- last.move_next(p)
-
- # Free mode
- # move border
- # and find intersections
- # with sides
- if self.auto_mode == 'ALL':
- s0 = self._axis.offset(-self.width)
- res, p0, t = self.segs[1].intersect(s0)
- if res:
- self.segs[2].p0 = p0
- self.segs[1].p1 = p0
- res, p1, t = self.segs[-1].intersect(s0)
- if res:
- self.segs[2].p1 = p1
- self.segs[-1].p0 = p1
-
- # /\
- # | angle
- # |____>
- #
- # v1 node -> next
- if self.side == 'LEFT':
- v1 = self._axis.v
- else:
- v1 = -self._axis.v
-
- if last.side == self.side:
- # contiguous, v0 node <- next
-
- # half angle between segments
- if self.side == 'LEFT':
- v0 = -last._axis.v
- else:
- v0 = last._axis.v
- da = v0.angle_signed(v1)
- if ccw:
- if da < 0:
- da = 2 * pi + da
- elif da > 0:
- da = da - 2 * pi
- last.next_link(0.5 * da)
-
- else:
- # alternate v0 node -> next
- # half angle between segments
- if last.side == 'LEFT':
- v0 = last._axis.v
- else:
- v0 = -last._axis.v
- da = v0.angle_signed(v1)
- # angle always ccw
- if ccw:
- if da < 0:
- da = 2 * pi + da
- elif da > 0:
- da = da - 2 * pi
- last.node_link(0.5 * da)
-
- self.node_link(-0.5 * da)
-
- def next_seg(self, index):
- idx = self.get_index(index + 1)
- return self.segs[idx]
-
- def last_seg(self, index):
- return self.segs[index - 1]
-
- def make_segments(self):
- if len(self.segs) < 1:
- s0 = self._axis
- w = self.width
- s1 = s0.straight(w, 1).rotate(pi / 2)
- s1.type = 'SIDE'
- s3 = s0.straight(w, 0).rotate(pi / 2).oposite
- s3.type = 'SIDE'
- s2 = StraightRoof(s1.p1, s3.p0 - s1.p1)
- s2.type = 'BOTTOM'
- self.segs = [s0, s1, s2, s3]
-
- def move_side(self, pt):
- """
- offset side to point
- """
- s2 = self.segs[2]
- d0, t = self.distance(s2.p0)
- d1, t = self.distance(pt)
- # adjust width and slope according
- self.width = d1
- self.slope = self.slope * d0 / d1
- self.segs[2] = s2.offset(d1 - d0)
-
- def propagate_backward(self, pt):
- """
- Propagate slope, keep 2d angle of slope
- Move first point and border
- keep border parallel
- adjust slope
- and next shape
- """
- # distance of p
- # offset side to point
- self.move_side(pt)
-
- # move verts on node side
- self.move_next(pt)
-
- if self.side == 'LEFT':
- # move verts on next side
- res, p, t = self.segs[-1].intersect(self.segs[2])
- else:
- # move verts on next side
- res, p, t = self.segs[1].intersect(self.segs[2])
-
- if res:
- self.move_node(p)
-
- if self.next is not None and self.next.auto_mode in {'AUTO'}:
- self.next.propagate_backward(p)
-
- def propagate_forward(self, pt):
- """
- Propagate slope, keep 2d angle of slope
- Move first point and border
- keep border parallel
- adjust slope
- and next shape
- """
- # offset side to point
- self.move_side(pt)
-
- # move verts on node side
- self.move_node(pt)
- if self.side == 'LEFT':
- # move verts on next side
- res, p, t = self.segs[1].intersect(self.segs[2])
- else:
- # move verts on next side
- res, p, t = self.segs[-1].intersect(self.segs[2])
-
- if res:
- self.move_next(p)
- if self.next is not None and self.next.auto_mode in {'AUTO'}:
- self.next.propagate_forward(p)
-
- def rotate_next_slope(self, a0):
- """
- Rotate next slope part
- """
- if self.side == 'LEFT':
- s0 = self.segs[1].rotate(a0)
- s1 = self.segs[2]
- res, p, t = s1.intersect(s0)
- else:
- s0 = self.segs[2]
- s1 = self.segs[-1]
- res, p, t = s1.oposite.rotate(-a0).intersect(s0)
-
- if res:
- s1.p0 = p
- s0.p1 = p
-
- if self.next is not None:
- if self.next.auto_mode == 'ALL':
- return
- if self.next.backward:
- self.next.propagate_backward(p)
- else:
- self.next.propagate_forward(p)
-
- def rotate_node_slope(self, a0):
- """
- Rotate node slope part
- """
- if self.side == 'LEFT':
- s0 = self.segs[2]
- s1 = self.segs[-1]
- res, p, t = s1.oposite.rotate(-a0).intersect(s0)
- else:
- s0 = self.segs[1].rotate(a0)
- s1 = self.segs[2]
- res, p, t = s1.intersect(s0)
-
- if res:
- s1.p0 = p
- s0.p1 = p
-
- if self.next is not None:
- if self.next.auto_mode == 'ALL':
- return
- if self.next.backward:
- self.next.propagate_backward(p)
- else:
- self.next.propagate_forward(p)
-
- def distance(self, pt):
- """
- distance from axis
- always use fake_axis here to
- allow axis being cut and
- still work
- """
- res, d, t = self.fake_axis.point_sur_segment(pt)
- return d, t
-
- def altitude(self, pt):
- d, t = self.distance(pt)
- return -d * self.slope
-
- def uv(self, pt):
- d, t = self.distance(pt)
- return ((t - self.tmin) * self.xsize, d)
-
- def intersect(self, seg):
- """
- compute intersections of a segment with boundaries
- segment must start on axis
- return segments inside
- """
- it = []
- for s in self.segs:
- res, p, t, u = seg.intersect_ext(s)
- if res:
- it.append((t, p))
- return it
-
- def merge(self, other):
-
- raise NotImplementedError
-
- def draw(self, context, z, verts, edges):
- f = len(verts)
- #
- # 0_______1
- # |_______|
- # 3 2
- verts.extend([(s.p0.x, s.p0.y, z + self.altitude(s.p0)) for s in self.segs])
- n_segs = len(self.segs) - 1
- edges.extend([[f + i, f + i + 1] for i in range(n_segs)])
- edges.append([f + n_segs, f])
- """
- f = len(verts)
- verts.extend([(s.p1.x, s.p1.y, z + self.altitude(s.p1)) for s in self.segs])
- n_segs = len(self.segs) - 1
- edges.extend([[f + i, f + i + 1] for i in range(n_segs)])
- edges.append([f + n_segs, f])
- """
- # holes
- for hole in self.holes:
- f = len(verts)
- #
- # 0_______1
- # |_______|
- # 3 2
- verts.extend([(s.p0.x, s.p0.y, z + self.altitude(s.p0)) for s in hole.segs])
- n_segs = len(hole.segs) - 1
- edges.extend([[f + i, f + i + 1] for i in range(n_segs)])
- edges.append([f + n_segs, f])
-
- # axis
- """
- f = len(verts)
- verts.extend([self.axis.p0.to_3d(), self.axis.p1.to_3d()])
- edges.append([f, f + 1])
-
- # cross
- f = len(verts)
- verts.extend([self.axis.lerp(0.5).to_3d(), (self.axis.lerp(0.5) + self.cross.v).to_3d()])
- edges.append([f, f + 1])
- """
-
- # relationships arrows
- if self.next or self.last:
- w = 0.2
- s0 = self._axis.offset(-0.5 * self.ysize)
- p0 = s0.lerp(0.4).to_3d()
- p0.z = z
- p1 = s0.lerp(0.6).to_3d()
- p1.z = z
- if self.side == 'RIGHT':
- p0, p1 = p1, p0
- if self.backward:
- p0, p1 = p1, p0
- s1 = s0.sized_normal(0.5, w)
- s2 = s0.sized_normal(0.5, -w)
- f = len(verts)
- p2 = s1.p1.to_3d()
- p2.z = z
- p3 = s2.p1.to_3d()
- p3.z = z
- verts.extend([p1, p0, p2, p3])
- edges.extend([[f + 1, f], [f + 2, f], [f + 3, f]])
-
- def as_string(self):
- """
- Print strips relationships
- """
- if self.backward:
- dir = "/\\"
- print("%s next" % (dir))
- else:
- dir = "\\/"
- print("%s node" % (dir))
- print("%s %s" % (dir, self.side))
- if self.backward:
- print("%s node" % (dir))
- else:
- print("%s next" % (dir))
- if self.next:
- print("_________")
- self.next.as_string()
- else:
- print("#########")
-
- def limits(self):
- dist = []
- param_t = []
- for s in self.segs:
- res, d, t = self.fake_axis.point_sur_segment(s.p0)
- param_t.append(t)
- dist.append(d)
-
- if len(param_t) > 0:
- self.tmin = min(param_t)
- self.tmax = max(param_t)
- else:
- self.tmin = 0
- self.tmax = 1
-
- self.dt = self.tmax - self.tmin
-
- if len(dist) > 0:
- self.ysize = max(dist)
- else:
- self.ysize = 0
-
- self.xsize = self.fake_axis.length * self.dt
- # vectors components of part matrix
- # where x is is axis direction
- # y down
- # z up
- vx = -self.fake_axis.v.normalized().to_3d()
- vy = Vector((-vx.y, vx.x, self.slope)).normalized()
- self.vx = vx
- self.vy = vy
- self.vz = vx.cross(vy)
-
-
-"""
-import bmesh
-m = C.object.data
-[(round(v.co.x, 3), round(v.co.y, 3), round(v.co.z, 3)) for v in m.vertices]
-[tuple(p.vertices) for p in m.polygons]
-
-uvs = []
-bpy.ops.object.mode_set(mode='EDIT')
-bm = bmesh.from_edit_mesh(m)
-[tuple(i.index for i in edge.verts) for edge in bm.edges]
-
-layer = bm.loops.layers.uv.verify()
-for i, face in enumerate(bm.faces):
- uv = []
- for j, loop in enumerate(face.loops):
- co = loop[layer].uv
- uv.append((round(co.x, 2), round(co.y, 2)))
- uvs.append(uv)
-uvs
-"""
-
-
-class RoofGenerator(CutAbleGenerator):
-
- def __init__(self, d, origin=Vector((0, 0, 0))):
- self.parts = d.parts
- self.segs = []
- self.nodes = []
- self.pans = []
- self.length = 0
- self.origin = origin.to_2d()
- self.z = origin.z
- self.width_right = d.width_right
- self.width_left = d.width_left
- self.slope_left = d.slope_left
- self.slope_right = d.slope_right
- self.user_defined_tile = None
- self.user_defined_uvs = None
- self.user_defined_mat = None
- self.is_t_child = d.t_parent != ""
-
- def add_part(self, part):
-
- if len(self.segs) < 1 or part.bound_idx < 1:
- s = None
- else:
- s = self.segs[part.bound_idx - 1]
-
- a0 = part.a0
-
- if part.constraint_type == 'SLOPE' and a0 == 0:
- a0 = 90
-
- # start a new roof
- if s is None:
- v = part.length * Vector((cos(a0), sin(a0)))
- s = StraightRoof(self.origin, v)
- else:
- s = s.straight_roof(a0, part.length)
-
- # parent segment (root) index is v0_idx - 1
- s.v0_idx = min(len(self.segs), part.bound_idx)
-
- s.constraint_type = part.constraint_type
-
- if part.constraint_type == 'SLOPE':
- s.enforce_part = part.enforce_part
- else:
- s.enforce_part = 'AUTO'
-
- s.angle_0 = a0
- s.take_precedence = part.take_precedence
- s.auto_right = part.auto_right
- s.auto_left = part.auto_left
- s.width_left = part.width_left
- s.width_right = part.width_right
- s.slope_left = part.slope_left
- s.slope_right = part.slope_right
- s.type = 'AXIS'
- s.triangular_end = part.triangular_end
- self.segs.append(s)
-
- def locate_manipulators(self):
- """
-
- """
- for i, f in enumerate(self.segs):
-
- manipulators = self.parts[i].manipulators
- p0 = f.p0.to_3d()
- p0.z = self.z
- p1 = f.p1.to_3d()
- p1.z = self.z
- # angle from last to current segment
- if i > 0:
-
- manipulators[0].type_key = 'ANGLE'
- v0 = self.segs[f.v0_idx - 1].straight(-1, 1).v.to_3d()
- v1 = f.straight(1, 0).v.to_3d()
- manipulators[0].set_pts([p0, v0, v1])
-
- # segment length
- manipulators[1].type_key = 'SIZE'
- manipulators[1].prop1_name = "length"
- manipulators[1].set_pts([p0, p1, (1.0, 0, 0)])
-
- # dumb segment id
- manipulators[2].set_pts([p0, p1, (1, 0, 0)])
-
- p0 = f.lerp(0.5).to_3d()
- p0.z = self.z
- # size left
- p1 = f.sized_normal(0.5, -self.parts[i].width_left).p1.to_3d()
- p1.z = self.z
- manipulators[3].set_pts([p0, p1, (1, 0, 0)])
-
- # size right
- p1 = f.sized_normal(0.5, self.parts[i].width_right).p1.to_3d()
- p1.z = self.z
- manipulators[4].set_pts([p0, p1, (-1, 0, 0)])
-
- # slope left
- n0 = f.sized_normal(0.5, -1)
- p0 = n0.p1.to_3d()
- p0.z = self.z
- p1 = p0.copy()
- p1.z = self.z - self.parts[i].slope_left
- manipulators[5].set_pts([p0, p1, (-1, 0, 0)], normal=n0.v.to_3d())
-
- # slope right
- n0 = f.sized_normal(0.5, 1)
- p0 = n0.p1.to_3d()
- p0.z = self.z
- p1 = p0.copy()
- p1.z = self.z - self.parts[i].slope_right
- manipulators[6].set_pts([p0, p1, (1, 0, 0)], normal=n0.v.to_3d())
-
- def seg_partition(self, array, begin, end):
- """
- sort tree segments by angle
- """
- pivot = begin
- for i in range(begin + 1, end + 1):
- if array[i].a0 < array[begin].a0:
- pivot += 1
- array[i], array[pivot] = array[pivot], array[i]
- array[pivot], array[begin] = array[begin], array[pivot]
- return pivot
-
- def sort_seg(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.seg_partition(array, begin, end)
- _quicksort(array, begin, pivot - 1)
- _quicksort(array, pivot + 1, end)
- return _quicksort(array, begin, end)
-
- def make_roof(self, context):
- """
- Init data structure for possibly multi branched nodes
- nodes : radial relationships
- pans : quad strip linear relationships
- """
-
- pans = []
-
- # node are connected segments
- # node
- # (segment idx)
- # (angle from root part > 0 right)
- # (reversed) a seg connected by p1
- # "root" of node
- nodes = [RoofAxisNode() for s in range(len(self.segs) + 1)]
-
- # Init width on seg 0
- s0 = self.segs[0]
- if self.parts[0].auto_left in {'AUTO', 'SLOPE'}:
- s0.width_left = self.width_left
- if self.parts[0].auto_right in {'AUTO', 'SLOPE'}:
- s0.width_right = self.width_right
- if self.parts[0].auto_left in {'AUTO', 'WIDTH'}:
- s0.slope_left = self.slope_left
- if self.parts[0].auto_left in {'AUTO', 'WIDTH'}:
- s0.slope_right = self.slope_right
-
- # make nodes with HORIZONTAL constraints
- for idx, s in enumerate(self.segs):
- s.v1_idx = idx + 1
- if s.constraint_type == 'HORIZONTAL':
- left = RoofPolygon(s, 'LEFT')
- right = RoofPolygon(s, 'RIGHT')
- left.other_side = right
- right.other_side = left
- rs = RoofSegment(s, left, right)
- pans.append(rs)
- nodes[s.v0_idx].add(s.angle_0, False, s, left, right)
- nodes[s.v1_idx].add(-pi, True, s, left, right)
-
- # set first node root
- # so regular sort does work
- nodes[0].root = nodes[0].segs[0]
- self.nodes = nodes
- # Propagate slope and width
- # on node basis along axis
- # bi-direction Radial around node
- # from left and right to center
- # contiguous -> same
- # T: and (x % 2 == 1)
- # First one take precedence over others
- # others inherit from side
- #
- # l / rb l = left
- # 3 r = right
- # l _1_ / b = backward
- # r \
- # 2
- # r\ l
- #
- # X: right one or left one l (x % 2 == 0)
- # inherits from side
- #
- # l 3 lb l = left
- # l__1_|_2_l r = right
- # r | r b = backward -> propagate in reverse axis direction
- # r 4 rb
- #
- # for idx, node in enumerate(nodes):
- # print("idx:%s node:%s" % (idx, node.root))
-
- for idx, node in enumerate(nodes):
-
- node.sort()
-
- nb_segs = node.count
-
- if node.root is None:
- continue
-
- left = node.root.left
- right = node.root.right
-
- # basic one single node
- if nb_segs < 2:
- left.make_segments()
- right.make_segments()
- continue
-
- # get "root" slope and width
- l_bind = left
- r_bind = right
-
- # simple case: 2 contiguous segments
- if nb_segs == 2:
- s = node.last
- s.right.bind(r_bind, ccw=False)
- s.left.bind(l_bind, ccw=True)
- continue
-
- # More than 2 segments, uneven distribution
- if nb_segs % 2 == 1:
- # find which child does take precedence
- # first one on rootline (arbitrary)
- center = (nb_segs - 1) / 2
- else:
- # even distribution
- center = nb_segs / 2
-
- # user defined precedence if any
- for i, s in enumerate(node.segs):
- if s.seg.take_precedence:
- center = i
- break
-
- # bind right side to center
- for i, s in enumerate(node.segs):
- # skip axis
- if i > 0:
- if i < center:
- # right contiguous with last
- s.right.bind(r_bind, ccw=False)
-
- # next bind to left
- r_bind = s.left
-
- # left backward, not bound
- # so setup width and slope
- if s.left.auto_mode in {'AUTO', 'WIDTH'}:
- s.left.slope = right.slope
- if s.left.auto_mode in {'AUTO', 'SLOPE'}:
- s.left.width = right.width
- s.left.backward = True
- else:
- # right bound to last
- s.right.bind(r_bind, ccw=False)
- break
-
- # bind left side to center
- for i, s in enumerate(reversed(node.segs)):
- # skip axis
- if i < nb_segs - center - 1:
- # left contiguous with last
- s.left.bind(l_bind, ccw=True)
- # next bind to right
- l_bind = s.right
- # right backward, not bound
- # so setup width and slope
- if s.right.auto_mode in {'AUTO', 'WIDTH'}:
- s.right.slope = left.slope
- if s.right.auto_mode in {'AUTO', 'SLOPE'}:
- s.right.width = left.width
- s.right.backward = True
- else:
- # right bound to last
- s.left.bind(l_bind, ccw=True)
- break
-
- # slope constraints allowed between segments
- # multiple (up to 2) on start and end
- # single between others
- #
- # 2 slope 2 slope 2 slope
- # | | |
- # |______section_1___|___section_2_____|
- # | | |
- # | | |
- # multiple single multiple
-
- # add slopes constraints to nodes
- for i, s in enumerate(self.segs):
- if s.constraint_type == 'SLOPE':
- nodes[s.v0_idx].add(s.angle_0, False, s, None, None)
-
- # sort nodes, remove duplicate slopes between
- # horizontal, keeping only first one
- for idx, node in enumerate(nodes):
- to_remove = []
- node.sort()
- # remove dup between all
- # but start / end nodes
- if node.n_horizontal > 1:
- last = None
- for i, s in enumerate(node.segs):
- if s.seg.constraint_type == last:
- if s.seg.constraint_type == 'SLOPE':
- to_remove.append(i)
- last = s.seg.constraint_type
- for i in reversed(to_remove):
- node.segs.pop(i)
- node.update_center()
-
- for idx, node in enumerate(nodes):
-
- # a node may contain many slopes
- # 2 * (part starting from node - 1)
- #
- # s0
- # root 0 |_______
- # |
- # s1
- #
- # s1
- # root _______|
- # |
- # s0
- #
- # s3 3 s2
- # l \l|r/ l
- # root ___\|/___ 2
- # r /|\ r
- # /r|l\
- # s0 1 s1
- #
- # s2 s1=slope
- # |r /
- # | / l
- # |/____s
- #
- # root to first child -> equal side
- # any other childs -> oposite sides
-
- if node.n_horizontal == 1:
- # slopes at start or end of segment
- # segment slope is not affected
- if node.n_slope > 0:
- # node has user def slope
- s = node.root
- s0 = node.left(node.center)
- a0 = s0.seg.delta_angle(s.seg)
- if node.root.reversed:
- # slope at end of segment
- # first one is right or left
- if a0 < 0:
- # right side
- res, p, t = s0.seg.intersect(s.right.segs[2])
- s.right.segs[-1].p0 = p
- s.right.segs[2].p1 = p
- else:
- # left side
- res, p, t = s0.seg.intersect(s.left.segs[2])
- s.left.segs[1].p1 = p
- s.left.segs[2].p0 = p
- if node.n_slope > 1:
- # last one must be left
- s1 = node.right(node.center)
- a1 = s1.seg.delta_angle(s.seg)
- # both slopes on same side:
- # skip this one
- if a0 > 0 and a1 < 0:
- # right side
- res, p, t = s1.seg.intersect(s.right.segs[2])
- s.right.segs[-1].p0 = p
- s.right.segs[2].p1 = p
- if a0 < 0 and a1 > 0:
- # left side
- res, p, t = s1.seg.intersect(s.left.segs[2])
- s.left.segs[1].p1 = p
- s.left.segs[2].p0 = p
-
- else:
- # slope at start of segment
- if a0 < 0:
- # right side
- res, p, t = s0.seg.intersect(s.right.segs[2])
- s.right.segs[1].p1 = p
- s.right.segs[2].p0 = p
- else:
- # left side
- res, p, t = s0.seg.intersect(s.left.segs[2])
- s.left.segs[-1].p0 = p
- s.left.segs[2].p1 = p
- if node.n_slope > 1:
- # last one must be right
- s1 = node.right(node.center)
- a1 = s1.seg.delta_angle(s.seg)
- # both slopes on same side:
- # skip this one
- if a0 > 0 and a1 < 0:
- # right side
- res, p, t = s1.seg.intersect(s.right.segs[2])
- s.right.segs[1].p1 = p
- s.right.segs[2].p0 = p
- if a0 < 0 and a1 > 0:
- # left side
- res, p, t = s1.seg.intersect(s.left.segs[2])
- s.left.segs[-1].p0 = p
- s.left.segs[2].p1 = p
-
- else:
- # slopes between segments
- # does change next segment slope
- for i, s0 in enumerate(node.segs):
- s1 = node.left(i)
- s2 = node.left(i + 1)
-
- if s1.seg.constraint_type == 'SLOPE':
-
- # 3 cases:
- # s0 is root contiguous -> sides are same
- # s2 is root contiguous -> sides are same
- # back to back -> sides are not same
-
- if s0.reversed:
- # contiguous right / right
- # 2 cases
- # right is backward
- # right is forward
- if s2.right.backward:
- # s0 depends on s2
- main = s2.right
- v = main.segs[1].v
- else:
- # s2 depends on s0
- main = s0.right
- v = -main.segs[-1].v
- res, p, t = s1.seg.intersect(main.segs[2])
- if res:
- # slope vector
- dp = p - s1.seg.p0
- a0 = dp.angle_signed(v)
- if s2.right.backward:
- main.rotate_node_slope(a0)
- else:
- main.rotate_next_slope(-a0)
- elif s2.reversed:
- # contiguous left / left
- # 2 cases
- # left is backward
- # left is forward
- if s0.left.backward:
- # s0 depends on s2
- main = s0.left
- v = -main.segs[-1].v
- else:
- # s2 depends on s0
- main = s2.left
- v = main.segs[1].v
- res, p, t = s1.seg.intersect(main.segs[2])
- if res:
- # slope vector
- dp = p - s1.seg.p0
- a0 = dp.angle_signed(v)
- if s0.left.backward:
- main.rotate_node_slope(-a0)
- else:
- main.rotate_next_slope(a0)
- else:
- # back left / right
- # 2 cases
- # left is backward
- # left is forward
- if s0.left.backward:
- # s2 depends on s0
- main = s0.left
- v = -main.segs[-1].v
- else:
- # s0 depends on s2
- main = s2.right
- v = main.segs[1].v
-
- res, p, t = s1.seg.intersect(main.segs[2])
- if res:
- # slope vector
- dp = p - s1.seg.p0
- a0 = dp.angle_signed(v)
- if s0.left.backward:
- main.rotate_node_slope(-a0)
- else:
- main.rotate_node_slope(a0)
-
- self.pans = []
-
- # triangular ends
- for node in self.nodes:
- if node.root is None:
- continue
- if node.n_horizontal == 1 and node.root.seg.triangular_end:
- if node.root.reversed:
- # Next side (segment end)
- left = node.root.left
- right = node.root.right
- left.next_tri = True
- right.next_tri = True
-
- s0 = left.segs[1]
- s1 = left.segs[2]
- s2 = right.segs[-1]
- s3 = right.segs[2]
- p0 = s1.lerp(-left.width / s1.length)
- p1 = s0.p0
- p2 = s3.lerp(1 + right.width / s3.length)
-
- # compute slope from points
- p3 = p0.to_3d()
- p3.z = -left.width * left.slope
- p4 = p1.to_3d()
- p5 = p2.to_3d()
- p5.z = -right.width * right.slope
- n = (p3 - p4).normalized().cross((p5 - p4).normalized())
- v = n.cross(Vector((0, 0, 1)))
- dz = n.cross(v)
-
- # compute axis
- s = StraightRoof(p1, v)
- res, d0, t = s.point_sur_segment(p0)
- res, d1, t = s.point_sur_segment(p2)
- p = RoofPolygon(s, 'RIGHT')
- p.make_segments()
- p.slope = -dz.z / dz.to_2d().length
- p.is_tri = True
-
- p.cross = StraightRoof(p1, (p2 - p0)).sized_normal(0, -1)
- p.next_cross = left.cross
- p.last_cross = right.cross
- right.next_cross = p.cross
- left.next_cross = p.cross
-
- # remove axis seg of tri
- p.segs[-1].p0 = p0
- p.segs[-1].p1 = p1
- p.segs[2].p0 = p2
- p.segs[2].p1 = p0
- p.segs[1].p1 = p2
- p.segs[1].p0 = p1
- p.segs[1].type = 'LINK_HIP'
- p.segs[-1].type = 'LINK_HIP'
- p.segs.pop(0)
- # adjust left and side borders
- s0.p1 = p0
- s1.p0 = p0
- s2.p0 = p2
- s3.p1 = p2
- s0.type = 'LINK_HIP'
- s2.type = 'LINK_HIP'
- self.pans.append(p)
-
- elif not self.is_t_child:
- # no triangular part with t_child
- # on "node" parent roof side
- left = node.root.left
- right = node.root.right
- left.node_tri = True
- right.node_tri = True
- s0 = right.segs[1]
- s1 = right.segs[2]
- s2 = left.segs[-1]
- s3 = left.segs[2]
- p0 = s1.lerp(-right.width / s1.length)
- p1 = s0.p0
- p2 = s3.lerp(1 + left.width / s3.length)
-
- # compute axis and slope from points
- p3 = p0.to_3d()
- p3.z = -right.width * right.slope
- p4 = p1.to_3d()
- p5 = p2.to_3d()
- p5.z = -left.width * left.slope
- n = (p3 - p4).normalized().cross((p5 - p4).normalized())
- v = n.cross(Vector((0, 0, 1)))
- dz = n.cross(v)
-
- s = StraightRoof(p1, v)
- p = RoofPolygon(s, 'RIGHT')
- p.make_segments()
- p.slope = -dz.z / dz.to_2d().length
- p.is_tri = True
-
- p.cross = StraightRoof(p1, (p2 - p0)).sized_normal(0, -1)
- p.next_cross = right.cross
- p.last_cross = left.cross
- right.last_cross = p.cross
- left.last_cross = p.cross
-
- # remove axis seg of tri
- p.segs[-1].p0 = p0
- p.segs[-1].p1 = p1
- p.segs[2].p0 = p2
- p.segs[2].p1 = p0
- p.segs[1].p1 = p2
- p.segs[1].p0 = p1
- p.segs[1].type = 'LINK_HIP'
- p.segs[-1].type = 'LINK_HIP'
- p.segs.pop(0)
- # adjust left and side borders
- s0.p1 = p0
- s1.p0 = p0
- s2.p0 = p2
- s3.p1 = p2
- s0.type = 'LINK_HIP'
- s2.type = 'LINK_HIP'
- self.pans.append(p)
-
- # make flat array
- for pan in pans:
- self.pans.extend([pan.left, pan.right])
-
- # merge contiguous with 0 angle diff
- to_remove = []
- for i, pan in enumerate(self.pans):
- if pan.backward:
- next = pan.last
- if next is not None:
- # same side only can merge
- if next.side == pan.side:
- if round(next._axis.delta_angle(pan._axis), 4) == 0:
- to_remove.append(i)
- next.next = pan.next
- next.last_cross = pan.last_cross
- next.node_tri = pan.node_tri
-
- next.slope = pan.slope
- if pan.side == 'RIGHT':
- if next.backward:
- next._axis.p1 = pan._axis.p1
- next.segs[1] = pan.segs[1]
- next.segs[2].p0 = pan.segs[2].p0
- else:
- next._axis.p0 = pan._axis.p0
- next.segs[-1] = pan.segs[-1]
- next.segs[2].p1 = pan.segs[2].p1
- else:
- if next.backward:
- next._axis.p0 = pan._axis.p0
- next.segs[-1] = pan.segs[-1]
- next.segs[2].p1 = pan.segs[2].p1
- else:
- next._axis.p1 = pan._axis.p1
- next.segs[1] = pan.segs[1]
- next.segs[2].p0 = pan.segs[2].p0
- else:
- next = pan.next
- if next is not None:
- # same side only can merge
- if next.side == pan.side:
- if round(next._axis.delta_angle(pan._axis), 4) == 0:
- to_remove.append(i)
- next.last = pan.last
- next.last_cross = pan.last_cross
- next.node_tri = pan.node_tri
-
- next.slope = pan.slope
- if pan.side == 'LEFT':
- if next.backward:
- next._axis.p1 = pan._axis.p1
- next.segs[1] = pan.segs[1]
- next.segs[2].p0 = pan.segs[2].p0
- else:
- next._axis.p0 = pan._axis.p0
- next.segs[-1] = pan.segs[-1]
- next.segs[2].p1 = pan.segs[2].p1
- else:
- if next.backward:
- next._axis.p0 = pan._axis.p0
- next.segs[-1] = pan.segs[-1]
- next.segs[2].p1 = pan.segs[2].p1
- else:
- next._axis.p1 = pan._axis.p1
- next.segs[1] = pan.segs[1]
- next.segs[2].p0 = pan.segs[2].p0
-
- for i in reversed(to_remove):
- self.pans.pop(i)
-
- # compute limits
- for pan in self.pans:
- pan.limits()
-
- """
- for pan in self.pans:
- if pan.last is None:
- pan.as_string()
- """
- return
-
- def lambris(self, context, o, d):
-
- idmat = 0
- lambris_height = 0.02
- alt = self.z - lambris_height
- for pan in self.pans:
-
- verts = []
- faces = []
- matids = []
- uvs = []
-
- f = len(verts)
- verts.extend([(s.p0.x, s.p0.y, alt + pan.altitude(s.p0)) for s in pan.segs])
- uvs.append([pan.uv(s.p0) for s in pan.segs])
- n_segs = len(pan.segs)
- face = [f + i for i in range(n_segs)]
- faces.append(face)
- matids.append(idmat)
-
- bm = bmed.buildmesh(
- context, o, verts, faces, matids=matids, uvs=uvs,
- weld=False, clean=False, auto_smooth=True, temporary=True)
-
- self.cut_holes(bm, pan)
-
- bmesh.ops.dissolve_limit(bm,
- angle_limit=0.01,
- use_dissolve_boundaries=False,
- verts=bm.verts,
- edges=bm.edges,
- delimit={'MATERIAL'})
-
- geom = bm.faces[:]
- verts = bm.verts[:]
- bmesh.ops.solidify(bm, geom=geom, thickness=0.0001)
- bmesh.ops.translate(bm, vec=Vector((0, 0, lambris_height)), space=o.matrix_world, verts=verts)
-
- # merge with object
- bmed.bmesh_join(context, o, [bm], normal_update=True)
-
- bpy.ops.object.mode_set(mode='OBJECT')
-
- def couverture(self, context, o, d):
-
- idmat = 7
- rand = 3
- ttl = len(self.pans)
- if ttl < 1:
- return
-
- sx, sy, sz = d.tile_size_x, d.tile_size_y, d.tile_size_z
-
- """
- /* Bevel offset_type slot values */
- enum {
- BEVEL_AMT_OFFSET,
- BEVEL_AMT_WIDTH,
- BEVEL_AMT_DEPTH,
- BEVEL_AMT_PERCENT
- };
- """
- offset_type = 'PERCENT'
-
- if d.tile_offset > 0:
- offset = - d.tile_offset / 100
- else:
- offset = 0
-
- if d.tile_model == 'BRAAS2':
- t_pts = [Vector(p) for p in [
- (0.06, -1.0, 1.0), (0.19, -1.0, 0.5), (0.31, -1.0, 0.5), (0.44, -1.0, 1.0),
- (0.56, -1.0, 1.0), (0.69, -1.0, 0.5), (0.81, -1.0, 0.5), (0.94, -1.0, 1.0),
- (0.06, 0.0, 0.5), (0.19, 0.0, 0.0), (0.31, 0.0, 0.0), (0.44, 0.0, 0.5),
- (0.56, 0.0, 0.5), (0.69, 0.0, 0.0), (0.81, 0.0, 0.0), (0.94, 0.0, 0.5),
- (-0.0, -1.0, 1.0), (-0.0, 0.0, 0.5), (1.0, -1.0, 1.0), (1.0, 0.0, 0.5)]]
- t_faces = [
- (16, 0, 8, 17), (0, 1, 9, 8), (1, 2, 10, 9), (2, 3, 11, 10),
- (3, 4, 12, 11), (4, 5, 13, 12), (5, 6, 14, 13), (6, 7, 15, 14), (7, 18, 19, 15)]
- elif d.tile_model == 'BRAAS1':
- t_pts = [Vector(p) for p in [
- (0.1, -1.0, 1.0), (0.2, -1.0, 0.5), (0.6, -1.0, 0.5), (0.7, -1.0, 1.0),
- (0.1, 0.0, 0.5), (0.2, 0.0, 0.0), (0.6, 0.0, 0.0), (0.7, 0.0, 0.5),
- (-0.0, -1.0, 1.0), (-0.0, 0.0, 0.5), (1.0, -1.0, 1.0), (1.0, 0.0, 0.5)]]
- t_faces = [(8, 0, 4, 9), (0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (3, 10, 11, 7)]
- elif d.tile_model == 'ETERNIT':
- t_pts = [Vector(p) for p in [
- (0.11, -1.0, 1.0), (0.9, -1.0, 1.0), (0.0, -0.79, 0.79),
- (1.0, -0.79, 0.79), (0.0, 2.0, -2.0), (1.0, 2.0, -2.0)]]
- t_faces = [(0, 1, 3, 5, 4, 2)]
- elif d.tile_model == 'ONDULEE':
- t_pts = [Vector(p) for p in [
- (0.0, -1.0, 0.1), (0.05, -1.0, 1.0), (0.1, -1.0, 0.1),
- (0.15, -1.0, 1.0), (0.2, -1.0, 0.1), (0.25, -1.0, 1.0),
- (0.3, -1.0, 0.1), (0.35, -1.0, 1.0), (0.4, -1.0, 0.1),
- (0.45, -1.0, 1.0), (0.5, -1.0, 0.1), (0.55, -1.0, 1.0),
- (0.6, -1.0, 0.1), (0.65, -1.0, 1.0), (0.7, -1.0, 0.1),
- (0.75, -1.0, 1.0), (0.8, -1.0, 0.1), (0.85, -1.0, 1.0),
- (0.9, -1.0, 0.1), (0.95, -1.0, 1.0), (1.0, -1.0, 0.1),
- (0.0, 0.0, 0.0), (0.05, 0.0, 0.9), (0.1, 0.0, 0.0),
- (0.15, 0.0, 0.9), (0.2, 0.0, 0.0), (0.25, 0.0, 0.9),
- (0.3, 0.0, 0.0), (0.35, 0.0, 0.9), (0.4, 0.0, 0.0),
- (0.45, 0.0, 0.9), (0.5, 0.0, 0.0), (0.55, 0.0, 0.9),
- (0.6, 0.0, 0.0), (0.65, 0.0, 0.9), (0.7, 0.0, 0.0),
- (0.75, 0.0, 0.9), (0.8, 0.0, 0.0), (0.85, 0.0, 0.9),
- (0.9, 0.0, 0.0), (0.95, 0.0, 0.9), (1.0, 0.0, 0.0)]]
- t_faces = [
- (0, 1, 22, 21), (1, 2, 23, 22), (2, 3, 24, 23),
- (3, 4, 25, 24), (4, 5, 26, 25), (5, 6, 27, 26),
- (6, 7, 28, 27), (7, 8, 29, 28), (8, 9, 30, 29),
- (9, 10, 31, 30), (10, 11, 32, 31), (11, 12, 33, 32),
- (12, 13, 34, 33), (13, 14, 35, 34), (14, 15, 36, 35),
- (15, 16, 37, 36), (16, 17, 38, 37), (17, 18, 39, 38),
- (18, 19, 40, 39), (19, 20, 41, 40)]
- elif d.tile_model == 'METAL':
- t_pts = [Vector(p) for p in [
- (0.0, -1.0, 0.0), (0.99, -1.0, 0.0), (1.0, -1.0, 0.0),
- (0.0, 0.0, 0.0), (0.99, 0.0, 0.0), (1.0, 0.0, 0.0),
- (0.99, -1.0, 1.0), (1.0, -1.0, 1.0), (1.0, 0.0, 1.0), (0.99, 0.0, 1.0)]]
- t_faces = [(0, 1, 4, 3), (7, 2, 5, 8), (1, 6, 9, 4), (6, 7, 8, 9)]
- elif d.tile_model == 'LAUZE':
- t_pts = [Vector(p) for p in [
- (0.75, -0.8, 0.8), (0.5, -1.0, 1.0), (0.25, -0.8, 0.8),
- (0.0, -0.5, 0.5), (1.0, -0.5, 0.5), (0.0, 0.5, -0.5), (1.0, 0.5, -0.5)]]
- t_faces = [(1, 0, 4, 6, 5, 3, 2)]
- elif d.tile_model == 'PLACEHOLDER':
- t_pts = [Vector(p) for p in [(0.0, -1.0, 1.0), (1.0, -1.0, 1.0), (0.0, 0.0, 0.0), (1.0, 0.0, 0.0)]]
- t_faces = [(0, 1, 3, 2)]
- elif d.tile_model == 'ROMAN':
- t_pts = [Vector(p) for p in [
- (0.18, 0.0, 0.3), (0.24, 0.0, 0.58), (0.76, 0.0, 0.58),
- (0.82, 0.0, 0.3), (0.05, -1.0, 0.5), (0.14, -1.0, 0.8),
- (0.86, -1.0, 0.8), (0.95, -1.0, 0.5), (0.45, 0.0, 0.5),
- (0.36, 0.0, 0.2), (-0.36, 0.0, 0.2), (-0.45, -0.0, 0.5),
- (0.32, -1.0, 0.7), (0.26, -1.0, 0.42), (-0.26, -1.0, 0.42),
- (-0.32, -1.0, 0.7), (0.5, 0.0, 0.74), (0.5, -1.0, 1.0),
- (-0.0, -1.0, 0.26), (-0.0, 0.0, 0.0)]
- ]
- t_faces = [
- (0, 4, 5, 1), (16, 17, 6, 2), (2, 6, 7, 3),
- (13, 12, 8, 9), (18, 13, 9, 19), (15, 14, 10, 11),
- (14, 18, 19, 10), (1, 5, 17, 16)
- ]
- elif d.tile_model == 'ROUND':
- t_pts = [Vector(p) for p in [
- (0.0, -0.5, 0.5), (1.0, -0.5, 0.5), (0.0, 0.0, 0.0),
- (1.0, 0.0, 0.0), (0.93, -0.71, 0.71), (0.78, -0.88, 0.88),
- (0.39, -0.97, 0.97), (0.61, -0.97, 0.97), (0.07, -0.71, 0.71),
- (0.22, -0.88, 0.88)]
- ]
- t_faces = [(6, 7, 5, 4, 1, 3, 2, 0, 8, 9)]
- else:
- return
-
- n_faces = len(t_faces)
- t_uvs = [[(t_pts[i].x, t_pts[i].y) for i in f] for f in t_faces]
-
- dx, dy = d.tile_space_x, d.tile_space_y
-
- step = 100 / ttl
-
- # if d.quick_edit:
- # context.scene.archipack_progress_text = "Build tiles:"
-
- for i, pan in enumerate(self.pans):
-
- seg = pan.fake_axis
- # compute base matrix top left of face
- vx = pan.vx
- vy = pan.vy
- vz = pan.vz
-
- x0, y0 = seg.lerp(pan.tmax)
- z0 = self.z + d.tile_altitude
- ysize_2d = (d.tile_border + pan.ysize)
- space_x = pan.xsize + 2 * d.tile_side
- space_y = ysize_2d * sqrt(1 + pan.slope * pan.slope)
- n_x = 1 + int(space_x / dx)
- n_y = 1 + int(space_y / dy)
-
- if d.tile_fit_x:
- dx = space_x / n_x
-
- if d.tile_fit_y:
- dy = space_y / n_y
-
- if d.tile_alternate:
- n_y += 1
-
- tM = Matrix([
- [vx.x, vy.x, vz.x, x0],
- [vx.y, vy.y, vz.y, y0],
- [vx.z, vy.z, vz.z, z0],
- [0, 0, 0, 1]
- ])
-
- verts = []
- faces = []
- matids = []
- uvs = []
-
- # steps for this pan
- substep = step / n_y
- # print("step:%s sub:%s" % (step, substep))
-
- for k in range(n_y):
-
- progress = step * i + substep * k
- # print("progress %s" % (progress))
- # if d.quick_edit:
- # context.scene.archipack_progress = progress
-
- y = k * dy
-
- x0 = offset * dx - d.tile_side
- nx = n_x
-
- if d.tile_alternate and k % 2 == 1:
- x0 -= 0.5 * dx
- nx += 1
-
- if d.tile_offset > 0:
- nx += 1
-
- for j in range(nx):
- x = x0 + j * dx
- lM = tM @ Matrix([
- [sx, 0, 0, x],
- [0, sy, 0, -y],
- [0, 0, sz, 0],
- [0, 0, 0, 1]
- ])
-
- v = len(verts)
-
- verts.extend([lM @ p for p in t_pts])
- faces.extend([tuple(i + v for i in f) for f in t_faces])
- mid = randint(idmat, idmat + rand)
- t_mats = [mid for i in range(n_faces)]
- matids.extend(t_mats)
- uvs.extend(t_uvs)
-
- # build temp bmesh and bissect
- bm = bmed.buildmesh(
- context, o, verts, faces, matids=matids, uvs=uvs,
- weld=False, clean=False, auto_smooth=True, temporary=True)
-
- # clean outer on convex parts
- # pan.convex = False
- remove = pan.convex
-
- for s in pan.segs:
- # seg without length lead to invalid normal
- if s.length > 0:
- if s.type == 'AXIS':
- self.bissect(bm, s.p1.to_3d(), s.cross_z.to_3d(), clear_outer=remove)
- elif s.type == 'BOTTOM':
- s0 = s.offset(d.tile_border)
- dz = pan.altitude(s0.p0)
- vx = s0.v.to_3d()
- vx.z = pan.altitude(s0.p1) - dz
- vy = vz.cross(vx.normalized())
- x, y = s0.p0
- z = z0 + dz
- self.bissect(bm, Vector((x, y, z)), -vy, clear_outer=remove)
- elif s.type == 'SIDE':
- p0 = s.p0 + s.cross_z.normalized() * d.tile_side
- self.bissect(bm, p0.to_3d(), s.cross_z.to_3d(), clear_outer=remove)
- elif s.type == 'LINK_VALLEY':
- p0 = s.p0 - s.cross_z.normalized() * d.tile_couloir
- self.bissect(bm, p0.to_3d(), s.cross_z.to_3d(), clear_outer=remove)
- elif s.type in {'LINK_HIP', 'LINK'}:
- self.bissect(bm, s.p0.to_3d(), s.cross_z.to_3d(), clear_outer=remove)
-
- # when not convex, select and remove outer parts
- if not pan.convex:
- """
- /* del "context" slot values, used for operator too */
- enum {
- DEL_VERTS = 1,
- DEL_EDGES,
- DEL_ONLYFACES,
- DEL_EDGESFACES,
- DEL_FACES,
- /* A version of 'DEL_FACES' that keeps edges on face boundaries,
- * allowing the surrounding edge-loop to be kept from removed face regions. */
- DEL_FACES_KEEP_BOUNDARY,
- DEL_ONLYTAGGED
- };
- """
- # Build boundary including borders and bottom offsets
- new_s = None
- segs = []
- for s in pan.segs:
- if s.length > 0:
- if s.type == 'LINK_VALLEY':
- offset = -d.tile_couloir
- elif s.type == 'BOTTOM':
- offset = d.tile_border
- elif s.type == 'SIDE':
- offset = d.tile_side
- else:
- offset = 0
- new_s = s.make_offset(offset, new_s)
- segs.append(new_s)
-
- if len(segs) > 0:
- # last / first intersection
- res, p, t = segs[0].intersect(segs[-1])
- if res:
- segs[0].p0 = p
- segs[-1].p1 = p
- f_geom = [f for f in bm.faces if not pan.inside(f.calc_center_median().to_2d(), segs)]
- if len(f_geom) > 0:
- bmesh.ops.delete(bm, geom=f_geom, context="FACES")
-
- self.cut_holes(bm, pan)
-
- bmesh.ops.dissolve_limit(bm,
- angle_limit=0.01,
- use_dissolve_boundaries=False,
- verts=bm.verts[:],
- edges=bm.edges[:],
- delimit={'MATERIAL'})
-
- if d.tile_bevel:
- geom = bm.verts[:]
- geom.extend(bm.edges[:])
- bmesh.ops.bevel(bm,
- geom=geom,
- offset=d.tile_bevel_amt,
- offset_type=offset_type,
- segments=d.tile_bevel_segs,
- profile=0.5,
- # vertex_only=False,
- clamp_overlap=True,
- material=-1)
-
- if d.tile_solidify:
- geom = bm.faces[:]
- verts = bm.verts[:]
- bmesh.ops.solidify(bm, geom=geom, thickness=0.0001)
- bmesh.ops.translate(bm, vec=vz * d.tile_height, space=o.matrix_world, verts=verts)
-
- # merge with object
- bmed.bmesh_join(context, o, [bm], normal_update=True)
- bpy.ops.object.mode_set(mode='OBJECT')
-
- # if d.quick_edit:
- # context.scene.archipack_progress = -1
-
- def _bargeboard(self, s, i, boundary, pan,
- width, height, altitude, offset, idmat,
- verts, faces, edges, matids, uvs):
-
- f = len(verts)
-
- s0 = s.offset(offset - width)
- s1 = s.offset(offset)
-
- p0 = s0.p0
- p1 = s1.p0
- p2 = s0.p1
- p3 = s1.p1
-
- s2 = boundary.last_seg(i)
- s3 = boundary.next_seg(i)
-
- if s2.type == 'SIDE':
- # intersect last seg offset
- s4 = s2.offset(offset - width)
- s5 = s2.offset(offset)
- res, p, t = s4.intersect(s0)
- if res:
- p0 = p
- res, p, t = s5.intersect(s1)
- if res:
- p1 = p
-
- elif s2.type == 'AXIS' or 'LINK' in s2.type:
- # intersect axis or link seg
- res, p, t = s2.intersect(s0)
- if res:
- p0 = p
- res, p, t = s2.intersect(s1)
- if res:
- p1 = p
-
- if s3.type == 'SIDE':
- # intersect next seg offset
- s4 = s3.offset(offset - width)
- s5 = s3.offset(offset)
- res, p, t = s4.intersect(s0)
- if res:
- p2 = p
- res, p, t = s5.intersect(s1)
- if res:
- p3 = p
-
- elif s3.type == 'AXIS' or 'LINK' in s3.type:
- # intersect axis or link seg
- res, p, t = s3.intersect(s0)
- if res:
- p2 = p
- res, p, t = s3.intersect(s1)
- if res:
- p3 = p
-
- x0, y0 = p0
- x1, y1 = p1
- x2, y2 = p3
- x3, y3 = p2
-
- z0 = self.z + altitude + pan.altitude(p0)
- z1 = self.z + altitude + pan.altitude(p1)
- z2 = self.z + altitude + pan.altitude(p3)
- z3 = self.z + altitude + pan.altitude(p2)
-
- verts.extend([
- (x0, y0, z0),
- (x1, y1, z1),
- (x2, y2, z2),
- (x3, y3, z3),
- ])
- z0 -= height
- z1 -= height
- z2 -= height
- z3 -= height
- verts.extend([
- (x0, y0, z0),
- (x1, y1, z1),
- (x2, y2, z2),
- (x3, y3, z3),
- ])
-
- faces.extend([
- # top
- (f, f + 1, f + 2, f + 3),
- # sides
- (f, f + 4, f + 5, f + 1),
- (f + 1, f + 5, f + 6, f + 2),
- (f + 2, f + 6, f + 7, f + 3),
- (f + 3, f + 7, f + 4, f),
- # bottom
- (f + 4, f + 7, f + 6, f + 5)
- ])
- edges.append([f, f + 3])
- edges.append([f + 1, f + 2])
- edges.append([f + 4, f + 7])
- edges.append([f + 5, f + 6])
-
- matids.extend([idmat, idmat, idmat, idmat, idmat, idmat])
- uvs.extend([
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)]
- ])
-
- def bargeboard(self, d, verts, faces, edges, matids, uvs):
-
- #####################
- # Vire-vents
- #####################
-
- idmat = 1
- for pan in self.pans:
-
- for hole in pan.holes:
- for i, s in enumerate(hole.segs):
- if s.type == 'SIDE':
- self._bargeboard(s,
- i,
- hole, pan,
- d.bargeboard_width,
- d.bargeboard_height,
- d.bargeboard_altitude,
- d.bargeboard_offset,
- idmat,
- verts,
- faces,
- edges,
- matids,
- uvs)
-
- for i, s in enumerate(pan.segs):
- if s.type == 'SIDE':
- self._bargeboard(s,
- i,
- pan, pan,
- d.bargeboard_width,
- d.bargeboard_height,
- d.bargeboard_altitude,
- d.bargeboard_offset,
- idmat,
- verts,
- faces,
- edges,
- matids,
- uvs)
-
- def _fascia(self, s, i, boundary, pan, tri_0, tri_1,
- width, height, altitude, offset, idmat,
- verts, faces, edges, matids, uvs):
-
- f = len(verts)
- s0 = s.offset(offset)
- s1 = s.offset(offset + width)
-
- s2 = boundary.last_seg(i)
- s3 = boundary.next_seg(i)
- s4 = s2
- s5 = s3
-
- p0 = s0.p0
- p1 = s1.p0
- p2 = s0.p1
- p3 = s1.p1
-
- # find last neighbor depending on type
- if s2.type == 'AXIS' or 'LINK' in s2.type:
- # apply only on boundaries
- if not s.is_hole:
- # use last axis
- if pan.side == 'LEFT':
- s6 = pan.next_cross
- else:
- s6 = pan.last_cross
- if tri_0:
- s2 = s.copy
- else:
- s2 = s2.oposite
- s2.v = (s.sized_normal(0, 1).v + s6.v).normalized()
- s4 = s2
-
- elif s2.type == 'SIDE':
- s2 = s.copy
- s2.type = 'SIDE'
- s2.v = s.sized_normal(0, 1).v
- s4 = s2
- else:
- s2 = s2.offset(offset)
- s4 = s2.offset(offset + width)
-
- # find next neighbor depending on type
- if s3.type == 'AXIS' or 'LINK' in s3.type:
- if not s.is_hole:
- # use last axis
- if pan.side == 'LEFT':
- s6 = pan.last_cross
- else:
- s6 = pan.next_cross
- if tri_1:
- s3 = s.oposite
- else:
- s3 = s3.copy
- s3.v = (s.sized_normal(0, 1).v + s6.v).normalized()
- s5 = s3
- elif s3.type == 'SIDE':
- # when next is side, use perpendicular
- s3 = s.oposite
- s3.type = 'SIDE'
- s3.v = s.sized_normal(0, 1).v
- s5 = s3
- else:
- s3 = s3.offset(offset)
- s5 = s3.offset(offset + width)
-
- # units vectors and scale
- # is unit normal on sides
- # print("s.p:%s, s.v:%s s1.p::%s s1.v::%s" % (s.p, s.v, s1.p, s1.v))
- res, p, t = s0.intersect(s2)
- if res:
- p0 = p
- res, p, t = s0.intersect(s3)
- if res:
- p1 = p
- res, p, t = s1.intersect(s4)
- if res:
- p2 = p
- res, p, t = s1.intersect(s5)
- if res:
- p3 = p
-
- x0, y0 = p0
- x1, y1 = p2
- x2, y2 = p3
- x3, y3 = p1
-
- z0 = self.z + altitude + pan.altitude(p0)
- z1 = self.z + altitude + pan.altitude(p2)
- z2 = self.z + altitude + pan.altitude(p3)
- z3 = self.z + altitude + pan.altitude(p1)
-
- verts.extend([
- (x0, y0, z0),
- (x1, y1, z1),
- (x2, y2, z2),
- (x3, y3, z3),
- ])
-
- z0 -= height
- z1 -= height
- z2 -= height
- z3 -= height
- verts.extend([
- (x0, y0, z0),
- (x1, y1, z1),
- (x2, y2, z2),
- (x3, y3, z3),
- ])
-
- faces.extend([
- # top
- (f, f + 1, f + 2, f + 3),
- # sides
- (f, f + 4, f + 5, f + 1),
- (f + 1, f + 5, f + 6, f + 2),
- (f + 2, f + 6, f + 7, f + 3),
- (f + 3, f + 7, f + 4, f),
- # bottom
- (f + 4, f + 7, f + 6, f + 5)
- ])
- edges.append([f, f + 3])
- edges.append([f + 1, f + 2])
- edges.append([f + 4, f + 7])
- edges.append([f + 5, f + 6])
- matids.extend([idmat, idmat, idmat, idmat, idmat, idmat])
- uvs.extend([
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)]
- ])
-
- def fascia(self, d, verts, faces, edges, matids, uvs):
-
- #####################
- # Larmiers
- #####################
-
- idmat = 2
- for pan in self.pans:
-
- for hole in pan.holes:
- for i, s in enumerate(hole.segs):
- if s.type == 'BOTTOM':
- self._fascia(s,
- i,
- hole, pan,
- False, False,
- d.fascia_width,
- d.fascia_height,
- d.fascia_altitude,
- d.fascia_offset,
- idmat,
- verts,
- faces,
- edges,
- matids,
- uvs)
-
- for i, s in enumerate(pan.segs):
- if s.type == 'BOTTOM':
-
- tri_0 = pan.node_tri
- tri_1 = pan.next_tri
-
- # triangular ends apply on boundary only
- # unless cut, boundary is parallel to axis
- # except for triangular ends
- if pan.side == 'LEFT':
- tri_0, tri_1 = tri_1, tri_0
-
- self._fascia(s,
- i,
- pan, pan,
- tri_0, tri_1,
- d.fascia_width,
- d.fascia_height,
- d.fascia_altitude,
- d.fascia_offset,
- idmat,
- verts,
- faces,
- edges,
- matids,
- uvs)
-
- continue
-
- f = len(verts)
- s0 = s.offset(d.fascia_width)
-
- s1 = pan.last_seg(i)
- s2 = pan.next_seg(i)
-
- # triangular ends apply on boundary only
- # unless cut, boundary is parallel to axis
- # except for triangular ends
-
- tri_0 = (pan.node_tri and not s.is_hole) or pan.is_tri
- tri_1 = (pan.next_tri and not s.is_hole) or pan.is_tri
-
- if pan.side == 'LEFT':
- tri_0, tri_1 = tri_1, tri_0
-
- # tiangular use bottom segment direction
- # find last neighbor depending on type
- if s1.type == 'AXIS' or 'LINK' in s1.type:
- # apply only on boundaries
- if not s.is_hole:
- # use last axis
- if pan.side == 'LEFT':
- s3 = pan.next_cross
- else:
- s3 = pan.last_cross
- if tri_0:
- s1 = s.copy
- else:
- s1 = s1.oposite
- s1.v = (s.sized_normal(0, 1).v + s3.v).normalized()
- elif s1.type == 'SIDE':
- s1 = s.copy
- s1.type = 'SIDE'
- s1.v = s.sized_normal(0, 1).v
- else:
- s1 = s1.offset(d.fascia_width)
-
- # find next neighbor depending on type
- if s2.type == 'AXIS' or 'LINK' in s2.type:
- if not s.is_hole:
- # use last axis
- if pan.side == 'LEFT':
- s3 = pan.last_cross
- else:
- s3 = pan.next_cross
- if tri_1:
- s2 = s.oposite
- else:
- s2 = s2.copy
- s2.v = (s.sized_normal(0, 1).v + s3.v).normalized()
- elif s2.type == 'SIDE':
- s2 = s.oposite
- s2.type = 'SIDE'
- s2.v = s.sized_normal(0, 1).v
- else:
-
- s2 = s2.offset(d.fascia_width)
-
- # units vectors and scale
- # is unit normal on sides
- # print("s.p:%s, s.v:%s s1.p::%s s1.v::%s" % (s.p, s.v, s1.p, s1.v))
- res, p0, t = s0.intersect(s1)
- res, p1, t = s0.intersect(s2)
-
- x0, y0 = s.p0
- x1, y1 = p0
- x2, y2 = p1
- x3, y3 = s.p1
- z0 = self.z + d.fascia_altitude + pan.altitude(s.p0)
- z1 = self.z + d.fascia_altitude + pan.altitude(s.p1)
- verts.extend([
- (x0, y0, z0),
- (x1, y1, z0),
- (x2, y2, z1),
- (x3, y3, z1),
- ])
- z0 -= d.fascia_height
- z1 -= d.fascia_height
- verts.extend([
- (x0, y0, z0),
- (x1, y1, z0),
- (x2, y2, z1),
- (x3, y3, z1),
- ])
-
- faces.extend([
- # top
- (f, f + 1, f + 2, f + 3),
- # sides
- (f, f + 4, f + 5, f + 1),
- (f + 1, f + 5, f + 6, f + 2),
- (f + 2, f + 6, f + 7, f + 3),
- (f + 3, f + 7, f + 4, f),
- # bottom
- (f + 4, f + 7, f + 6, f + 5)
- ])
- edges.append([f, f + 3])
- edges.append([f + 1, f + 2])
- edges.append([f + 4, f + 7])
- edges.append([f + 5, f + 6])
- matids.extend([idmat, idmat, idmat, idmat, idmat, idmat])
- uvs.extend([
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)]
- ])
-
- def gutter(self, d, verts, faces, edges, matids, uvs):
-
- #####################
- # Chenaux
- #####################
-
- idmat = 5
-
- # caps at start and end
- if d.gutter_segs % 2 == 1:
- n_faces = int((d.gutter_segs - 1) / 2)
- else:
- n_faces = int((d.gutter_segs / 2) - 1)
-
- df = 2 * d.gutter_segs + 1
-
- for pan in self.pans:
- for i, s in enumerate(pan.segs):
-
- if s.type == 'BOTTOM':
- f = len(verts)
-
- s0 = s.offset(d.gutter_dist + d.gutter_width)
-
- s1 = pan.last_seg(i)
- s2 = pan.next_seg(i)
-
- p0 = s0.p0
- p1 = s0.p1
-
- tri_0 = pan.node_tri or pan.is_tri
- tri_1 = pan.next_tri or pan.is_tri
-
- if pan.side == 'LEFT':
- tri_0, tri_1 = tri_1, tri_0
-
- f = len(verts)
-
- # tiangular use segment direction
- # find last neighbor depending on type
- if s1.type == 'AXIS' or 'LINK' in s1.type:
- # apply only on boundaries
- if not s.is_hole:
- # use last axis
- if pan.side == 'LEFT':
- s3 = pan.next_cross
- else:
- s3 = pan.last_cross
- if tri_0:
- s1 = s.copy
- else:
- s1 = s1.oposite
- s1.v = (s.sized_normal(0, 1).v + s3.v).normalized()
- elif s1.type == 'SIDE':
- s1 = s.copy
- s1.type = 'SIDE'
- s1.v = s.sized_normal(0, 1).v
- else:
- s1 = s1.offset(d.gutter_dist + d.gutter_width)
-
- # find next neighbor depending on type
- if s2.type == 'AXIS' or 'LINK' in s2.type:
- if not s.is_hole:
- # use last axis
- if pan.side == 'LEFT':
- s3 = pan.last_cross
- else:
- s3 = pan.next_cross
- if tri_1:
- s2 = s.oposite
- else:
- s2 = s2.copy
- s2.v = (s.sized_normal(0, 1).v + s3.v).normalized()
- elif s2.type == 'SIDE':
- s2 = s.oposite
- s2.type = 'SIDE'
- s2.v = s.sized_normal(0, 1).v
- else:
- s2 = s2.offset(d.gutter_dist + d.gutter_width)
-
- # units vectors and scale
- # is unit normal on sides
- # print("s.p:%s, s.v:%s s1.p::%s s1.v::%s" % (s.p, s.v, s1.p, s1.v))
- res, p, t = s0.intersect(s1)
- if res:
- p0 = p
- res, p, t = s0.intersect(s2)
- if res:
- p1 = p
- """
- f = len(verts)
- verts.extend([s1.p0.to_3d(), s1.p1.to_3d()])
- edges.append([f, f + 1])
-
- f = len(verts)
- verts.extend([s2.p0.to_3d(), s2.p1.to_3d()])
- edges.append([f, f + 1])
- continue
- """
-
- v0 = p0 - s.p0
- v1 = p1 - s.p1
-
- scale_0 = v0.length / (d.gutter_dist + d.gutter_width)
- scale_1 = v1.length / (d.gutter_dist + d.gutter_width)
-
- s3 = Line(s.p0, v0.normalized())
- s4 = Line(s.p1, v1.normalized())
-
- zt = self.z + d.fascia_altitude + pan.altitude(s3.p0)
- z0 = self.z + d.gutter_alt + pan.altitude(s3.p0)
- z1 = z0 - 0.5 * d.gutter_width
- z2 = z1 - 0.5 * d.gutter_width
- z3 = z1 - 0.5 * d.gutter_boudin
- dz0 = z2 - z1
- dz1 = z3 - z1
-
- tt = scale_0 * d.fascia_width
- t0 = scale_0 * d.gutter_dist
- t1 = t0 + scale_0 * (0.5 * d.gutter_width)
- t2 = t1 + scale_0 * (0.5 * d.gutter_width)
- t3 = t2 + scale_0 * (0.5 * d.gutter_boudin)
-
- # bord tablette
- xt, yt = s3.lerp(tt)
-
- # bord
- x0, y0 = s3.lerp(t0)
- # axe chenaux
- x1, y1 = s3.lerp(t1)
- # bord boudin interieur
- x2, y2 = s3.lerp(t2)
- # axe boudin
- x3, y3 = s3.lerp(t3)
-
- dx = x0 - x1
- dy = y0 - y1
-
- verts.append((xt, yt, zt))
- # chenaux
- da = pi / d.gutter_segs
- for i in range(d.gutter_segs):
- sa = sin(i * da)
- ca = cos(i * da)
- verts.append((x1 + dx * ca, y1 + dy * ca, z1 + dz0 * sa))
-
- dx = x2 - x3
- dy = y2 - y3
-
- # boudin
- da = -pi / (0.75 * d.gutter_segs)
- for i in range(d.gutter_segs):
- sa = sin(i * da)
- ca = cos(i * da)
- verts.append((x3 + dx * ca, y3 + dy * ca, z1 + dz1 * sa))
-
- zt = self.z + d.fascia_altitude + pan.altitude(s4.p0)
- z0 = self.z + d.gutter_alt + pan.altitude(s4.p0)
- z1 = z0 - 0.5 * d.gutter_width
- z2 = z1 - 0.5 * d.gutter_width
- z3 = z1 - 0.5 * d.gutter_boudin
- dz0 = z2 - z1
- dz1 = z3 - z1
- tt = scale_1 * d.fascia_width
- t0 = scale_1 * d.gutter_dist
- t1 = t0 + scale_1 * (0.5 * d.gutter_width)
- t2 = t1 + scale_1 * (0.5 * d.gutter_width)
- t3 = t2 + scale_1 * (0.5 * d.gutter_boudin)
-
- # bord tablette
- xt, yt = s4.lerp(tt)
-
- # bord
- x0, y0 = s4.lerp(t0)
- # axe chenaux
- x1, y1 = s4.lerp(t1)
- # bord boudin interieur
- x2, y2 = s4.lerp(t2)
- # axe boudin
- x3, y3 = s4.lerp(t3)
-
- dx = x0 - x1
- dy = y0 - y1
-
- # tablette
- verts.append((xt, yt, zt))
- faces.append((f + df, f, f + 1, f + df + 1))
- uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
- matids.append(idmat)
-
- # chenaux
- da = pi / d.gutter_segs
- for i in range(d.gutter_segs):
- sa = sin(i * da)
- ca = cos(i * da)
- verts.append((x1 + dx * ca, y1 + dy * ca, z1 + dz0 * sa))
-
- dx = x2 - x3
- dy = y2 - y3
-
- # boudin
- da = -pi / (0.75 * d.gutter_segs)
- for i in range(d.gutter_segs):
- sa = sin(i * da)
- ca = cos(i * da)
- verts.append((x3 + dx * ca, y3 + dy * ca, z1 + dz1 * sa))
-
- df = 2 * d.gutter_segs + 1
-
- for i in range(1, 2 * d.gutter_segs):
- j = i + f
- faces.append((j, j + df, j + df + 1, j + 1))
- uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
- matids.append(idmat)
-
- """
- segs = 6
-
- n_faces = segs / 2 - 1
-
- 0 6
- 1 5
- 2 4
- 3
- """
- # close start
- if s1.type == 'SIDE':
-
- if d.gutter_segs % 2 == 0:
- faces.append((f + n_faces + 3, f + n_faces + 1, f + n_faces + 2))
- uvs.append([(0, 0), (1, 0), (0.5, -0.5)])
- matids.append(idmat)
-
- for i in range(n_faces):
-
- j = i + f + 1
- k = f + d.gutter_segs - i
- faces.append((j + 1, k, k + 1, j))
- uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
- matids.append(idmat)
-
- # close end
- if s2.type == 'SIDE':
-
- f += 2 * d.gutter_segs + 1
-
- if d.gutter_segs % 2 == 0:
- faces.append((f + n_faces + 1, f + n_faces + 3, f + n_faces + 2))
- uvs.append([(0, 0), (1, 0), (0.5, -0.5)])
- matids.append(idmat)
-
- for i in range(n_faces):
-
- j = i + f + 1
- k = f + d.gutter_segs - i
- faces.append((j, k + 1, k, j + 1))
- uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
- matids.append(idmat)
-
- def beam_primary(self, d, verts, faces, edges, matids, uvs):
-
- idmat = 3
-
- for pan in self.pans:
- for i, s in enumerate(pan.segs):
-
- if s.type == 'AXIS':
-
- ####################
- # Poutre Faitiere
- ####################
-
- """
- 1___________________2 left
- 0|___________________|3 axis
- |___________________| right
- 5 4
- """
- f = len(verts)
-
- s2 = s.offset(-0.5 * d.beam_width)
-
- # offset from roof border
- s0 = pan.last_seg(i)
- s1 = pan.next_seg(i)
- t0 = 0
- t1 = 1
-
- s0_tri = pan.next_tri
- s1_tri = pan.node_tri
-
- if pan.side == 'LEFT':
- s0_tri, s1_tri = s1_tri, s0_tri
-
- if s0.type == 'SIDE' and s.length > 0:
- s0 = s0.offset(d.beam_offset)
- t0 = -d.beam_offset / s.length
-
- if s0_tri:
- p0 = s2.p0
- t0 = 0
- else:
- res, p0, t = s2.intersect(s0)
- if not res:
- continue
-
- if s1.type == 'SIDE' and s.length > 0:
- s1 = s1.offset(d.beam_offset)
- t1 = 1 + d.beam_offset / s.length
-
- if s1_tri:
- t1 = 1
- p1 = s2.p1
- else:
- res, p1, t = s2.intersect(s1)
- if not res:
- continue
-
- x0, y0 = p0
- x1, y1 = s.lerp(t0)
- x2, y2 = p1
- x3, y3 = s.lerp(t1)
- z0 = self.z + d.beam_alt + pan.altitude(p0)
- z1 = z0 - d.beam_height
- z2 = self.z + d.beam_alt + pan.altitude(p1)
- z3 = z2 - d.beam_height
- verts.extend([
- (x0, y0, z0),
- (x1, y1, z0),
- (x2, y2, z2),
- (x3, y3, z2),
- (x0, y0, z1),
- (x1, y1, z1),
- (x2, y2, z3),
- (x3, y3, z3),
- ])
- if s0_tri or s0.type == 'SIDE':
- faces.append((f + 4, f + 5, f + 1, f))
- uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
- matids.append(idmat)
- if s1_tri or s1.type == 'SIDE':
- faces.append((f + 2, f + 3, f + 7, f + 6))
- uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
- matids.append(idmat)
-
- faces.extend([
- # internal side
- # (f + 1, f + 5, f + 7, f + 3),
- # external side
- (f + 2, f + 6, f + 4, f),
- # top
- (f, f + 1, f + 3, f + 2),
- # bottom
- (f + 5, f + 4, f + 6, f + 7)
- ])
- matids.extend([
- idmat, idmat, idmat
- ])
- uvs.extend([
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)],
- [(0, 0), (0, 1), (1, 1), (1, 0)]
- ])
-
- def rafter(self, context, o, d):
-
- idmat = 4
-
- # Rafters / Chevrons
- start = max(0.001 + 0.5 * d.rafter_width, d.rafter_start)
-
- holes_offset = -d.rafter_width
-
- # build temp bmesh and bissect
- for pan in self.pans:
- tmin, tmax, ysize = pan.tmin, pan.tmax, pan.ysize
-
- # print("tmin:%s tmax:%s ysize:%s" % (tmin, tmax, ysize))
-
- f = 0
-
- verts = []
- faces = []
- matids = []
- uvs = []
- alt = d.rafter_alt
- seg = pan.fake_axis
-
- t0 = tmin + (start - 0.5 * d.rafter_width) / seg.length
- t1 = tmin + (start + 0.5 * d.rafter_width) / seg.length
-
- tx = start / seg.length
- dt = d.rafter_spacing / seg.length
-
- n_items = max(1, round((tmax - tmin) / dt, 0))
-
- dt = ((tmax - tmin) - 2 * tx) / n_items
-
- for j in range(int(n_items) + 1):
- n0 = seg.sized_normal(t1 + j * dt, - ysize)
- n1 = seg.sized_normal(t0 + j * dt, - ysize)
- f = len(verts)
-
- z0 = self.z + alt + pan.altitude(n0.p0)
- x0, y0 = n0.p0
- z1 = self.z + alt + pan.altitude(n0.p1)
- x1, y1 = n0.p1
- z2 = self.z + alt + pan.altitude(n1.p0)
- x2, y2 = n1.p0
- z3 = self.z + alt + pan.altitude(n1.p1)
- x3, y3 = n1.p1
-
- verts.extend([
- (x0, y0, z0),
- (x1, y1, z1),
- (x2, y2, z2),
- (x3, y3, z3)
- ])
-
- faces.append((f + 1, f, f + 2, f + 3))
- matids.append(idmat)
- uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
-
- bm = bmed.buildmesh(
- context, o, verts, faces, matids=matids, uvs=uvs,
- weld=False, clean=False, auto_smooth=True, temporary=True)
-
- self.cut_boundary(bm, pan)
- self.cut_holes(bm, pan, offset={'DEFAULT': holes_offset})
-
- bmesh.ops.dissolve_limit(bm,
- angle_limit=0.01,
- use_dissolve_boundaries=False,
- verts=bm.verts,
- edges=bm.edges,
- delimit={'MATERIAL'})
-
- geom = bm.faces[:]
- verts = bm.verts[:]
- bmesh.ops.solidify(bm, geom=geom, thickness=0.0001)
- bmesh.ops.translate(bm, vec=Vector((0, 0, -d.rafter_height)), space=o.matrix_world, verts=verts)
- # uvs for sides
- uvs = [(0, 0), (1, 0), (1, 1), (0, 1)]
- layer = bm.loops.layers.uv.verify()
- for i, face in enumerate(bm.faces):
- if len(face.loops) == 4:
- for j, loop in enumerate(face.loops):
- loop[layer].uv = uvs[j]
-
- # merge with object
- bmed.bmesh_join(context, o, [bm], normal_update=True)
-
- bpy.ops.object.mode_set(mode='OBJECT')
-
- def hips(self, d, verts, faces, edges, matids, uvs):
-
- idmat_valley = 5
- idmat = 6
- idmat_poutre = 4
-
- sx, sy, sz = d.hip_size_x, d.hip_size_y, d.hip_size_z
-
- if d.hip_model == 'ROUND':
-
- # round hips
- t_pts = [Vector((sx * x, sy * y, sz * z)) for x, y, z in [
- (-0.5, 0.34, 0.08), (-0.5, 0.32, 0.19), (0.5, -0.4, -0.5),
- (0.5, 0.4, -0.5), (-0.5, 0.26, 0.28), (-0.5, 0.16, 0.34),
- (-0.5, 0.05, 0.37), (-0.5, -0.05, 0.37), (-0.5, -0.16, 0.34),
- (-0.5, -0.26, 0.28), (-0.5, -0.32, 0.19), (-0.5, -0.34, 0.08),
- (-0.5, -0.25, -0.5), (-0.5, 0.25, -0.5), (0.5, -0.08, 0.5),
- (0.5, -0.5, 0.08), (0.5, -0.24, 0.47), (0.5, -0.38, 0.38),
- (0.5, -0.47, 0.24), (0.5, 0.5, 0.08), (0.5, 0.08, 0.5),
- (0.5, 0.47, 0.24), (0.5, 0.38, 0.38), (0.5, 0.24, 0.47)
- ]]
- t_faces = [
- (23, 22, 4, 5), (3, 19, 21, 22, 23, 20, 14, 16, 17, 18, 15, 2), (14, 20, 6, 7),
- (18, 17, 9, 10), (15, 18, 10, 11), (21, 19, 0, 1), (17, 16, 8, 9),
- (13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 1, 0), (19, 3, 13, 0), (20, 23, 5, 6), (22, 21, 1, 4),
- (3, 2, 12, 13), (2, 15, 11, 12), (16, 14, 7, 8)
- ]
- t_uvs = [
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.5, 1.0), (0.75, 0.93), (0.93, 0.75),
- (1.0, 0.5), (0.93, 0.25), (0.75, 0.07),
- (0.5, 0.0), (0.25, 0.07), (0.07, 0.25),
- (0.0, 0.5), (0.07, 0.75), (0.25, 0.93)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.5, 1.0), (0.75, 0.93), (0.93, 0.75),
- (1.0, 0.5), (0.93, 0.25), (0.75, 0.07),
- (0.5, 0.0), (0.25, 0.07), (0.07, 0.25),
- (0.0, 0.5), (0.07, 0.75), (0.25, 0.93)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
- ]
- # affect vertex with slope
- t_left = []
- t_right = []
-
- elif d.hip_model == 'ETERNIT':
-
- # square hips "eternit like"
- t_pts = [Vector((sx * x, sy * y, sz * z)) for x, y, z in [
- (0.5, 0.5, 0.0), (-0.5, 0.5, -0.5), (0.5, -0.5, 0.0),
- (-0.5, -0.5, -0.5), (0.5, 0.0, 0.0), (-0.5, -0.0, -0.5),
- (0.5, 0.0, 0.5), (0.5, -0.5, 0.5), (-0.5, -0.5, 0.0),
- (-0.5, -0.0, 0.0), (0.5, 0.5, 0.5), (-0.5, 0.5, 0.0)]
- ]
- t_faces = [
- (4, 2, 3, 5), (0, 4, 5, 1), (6, 9, 8, 7),
- (10, 11, 9, 6), (0, 10, 6, 4), (5, 9, 11, 1),
- (2, 7, 8, 3), (1, 11, 10, 0), (4, 6, 7, 2), (3, 8, 9, 5)
- ]
- t_uvs = [
- [(0.0, 0.5), (0.0, 1.0), (1.0, 1.0), (1.0, 0.5)], [(0.0, 0.0), (0.0, 0.5), (1.0, 0.5), (1.0, 0.0)],
- [(0.0, 0.5), (1.0, 0.5), (1.0, 1.0), (0.0, 1.0)], [(0.0, 0.0), (1.0, 0.0), (1.0, 0.5), (0.0, 0.5)],
- [(0.0, 0.5), (0.0, 1.0), (0.5, 1.0), (0.5, 0.5)], [(0.5, 0.5), (0.5, 1.0), (0.0, 1.0), (0.0, 0.5)],
- [(0.0, 0.5), (0.0, 1.0), (1.0, 1.0), (1.0, 0.5)], [(0.0, 0.5), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.5)],
- [(0.5, 0.5), (0.5, 1.0), (1.0, 1.0), (1.0, 0.5)], [(0.0, 0.5), (0.0, 1.0), (-0.5, 1.0), (-0.5, 0.5)]
- ]
- t_left = [2, 3, 7, 8]
- t_right = [0, 1, 10, 11]
-
- elif d.hip_model == 'FLAT':
- # square hips "eternit like"
- t_pts = [Vector((sx * x, sy * y, sz * z)) for x, y, z in [
- (-0.5, -0.4, 0.0), (-0.5, -0.4, 0.5), (-0.5, 0.4, 0.0),
- (-0.5, 0.4, 0.5), (0.5, -0.5, 0.5), (0.5, -0.5, 1.0),
- (0.5, 0.5, 0.5), (0.5, 0.5, 1.0), (-0.5, 0.33, 0.0),
- (-0.5, -0.33, 0.0), (0.5, -0.33, 0.5), (0.5, 0.33, 0.5),
- (-0.5, 0.33, -0.5), (-0.5, -0.33, -0.5), (0.5, -0.33, -0.5),
- (0.5, 0.33, -0.5)]
- ]
- t_faces = [
- (0, 1, 3, 2, 8, 9), (2, 3, 7, 6), (6, 7, 5, 4, 10, 11),
- (4, 5, 1, 0), (9, 10, 4, 0), (7, 3, 1, 5),
- (2, 6, 11, 8), (9, 8, 12, 13), (12, 15, 14, 13),
- (8, 11, 15, 12), (10, 9, 13, 14), (11, 10, 14, 15)]
- t_uvs = [
- [(0.5, 1.0), (0.93, 0.75), (0.93, 0.25), (0.5, 0.0), (0.07, 0.25), (0.07, 0.75)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.5, 1.0), (0.93, 0.75), (0.93, 0.25), (0.5, 0.0), (0.07, 0.25), (0.07, 0.75)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
- [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
- ]
- t_left = []
- t_right = []
-
- t_idmats = [idmat for f in t_faces]
-
- for pan in self.pans:
- for i, s in enumerate(pan.segs):
- if ('LINK' in s.type and
- d.beam_sec_enable):
- ##############
- # beam inside
- ##############
- f = len(verts)
-
- s0 = s.offset(-0.5 * d.beam_sec_width)
-
- s2 = pan.last_seg(i)
- s3 = pan.next_seg(i)
- p0 = s0.p0
- p1 = s0.p1
- t0 = 0
- t1 = 1
- res, p, t = s0.intersect(s2)
- if res:
- t0 = t
- p0 = p
- res, p, t = s0.intersect(s3)
- if res:
- t1 = t
- p1 = p
-
- p0 = s.lerp(t0)
- p1 = s.lerp(t1)
-
- x0, y0 = s0.lerp(t0)
- x1, y1 = s.p0
-
- z0 = self.z + d.beam_sec_alt + pan.altitude(p0)
- z1 = z0 - d.beam_sec_height
- z2 = self.z + d.beam_sec_alt + pan.altitude(s.p0)
- z3 = z2 - d.beam_sec_height
-
- verts.extend([
- (x0, y0, z0),
- (x0, y0, z1),
- (x1, y1, z2),
- (x1, y1, z3)
- ])
-
- x2, y2 = s0.lerp(t1)
- x3, y3 = s.p1
-
- z0 = self.z + d.beam_sec_alt + pan.altitude(p1)
- z1 = z0 - d.beam_sec_height
- z2 = self.z + d.beam_sec_alt + pan.altitude(s.p1)
- z3 = z2 - d.beam_sec_height
-
- verts.extend([
- (x2, y2, z0),
- (x2, y2, z1),
- (x3, y3, z2),
- (x3, y3, z3)
- ])
-
- faces.extend([
- (f, f + 4, f + 5, f + 1),
- (f + 1, f + 5, f + 7, f + 3),
- (f + 2, f + 3, f + 7, f + 6),
- (f + 2, f + 6, f + 4, f),
- (f, f + 1, f + 3, f + 2),
- (f + 5, f + 4, f + 6, f + 7)
- ])
- matids.extend([
- idmat_poutre, idmat_poutre, idmat_poutre,
- idmat_poutre, idmat_poutre, idmat_poutre
- ])
- uvs.extend([
- [(0, 0), (1, 0), (1, 1), (0, 1)],
- [(0, 0), (1, 0), (1, 1), (0, 1)],
- [(0, 0), (1, 0), (1, 1), (0, 1)],
- [(0, 0), (1, 0), (1, 1), (0, 1)],
- [(0, 0), (1, 0), (1, 1), (0, 1)],
- [(0, 0), (1, 0), (1, 1), (0, 1)]
- ])
-
- if s.type == 'LINK_HIP':
-
- # TODO:
- # Slice borders properly
-
- if d.hip_enable:
-
- s0 = pan.last_seg(i)
- s1 = pan.next_seg(i)
- s2 = s
- p0 = s0.p1
- p1 = s1.p0
- z0 = pan.altitude(p0)
- z1 = pan.altitude(p1)
-
- # s0 is top seg
- if z1 > z0:
- p0, p1 = p1, p0
- z0, z1 = z1, z0
- s2 = s2.oposite
- dz = pan.altitude(s2.sized_normal(0, 1).p1) - z0
-
- if dz < 0:
- s1 = s1.offset(d.tile_border)
- # vx from p0 to p1
- x, y = p1 - p0
- v = Vector((x, y, z1 - z0))
- vx = v.normalized()
- vy = vx.cross(Vector((0, 0, 1)))
- vz = vy.cross(vx)
-
- x0, y0 = p0 + d.hip_alt * vz.to_2d()
- z2 = z0 + self.z + d.hip_alt * vz.z
- tM = Matrix([
- [vx.x, vy.x, vz.x, x0],
- [vx.y, vy.y, vz.y, y0],
- [vx.z, vy.z, vz.z, z2],
- [0, 0, 0, 1]
- ])
- space_x = v.length - d.tile_border
- n_x = 1 + int(space_x / d.hip_space_x)
- dx = space_x / n_x
- x0 = 0.5 * dx
-
- t_verts = [p for p in t_pts]
-
- # apply slope
-
- for i in t_left:
- t_verts[i] = t_verts[i].copy()
- t_verts[i].z -= dz * t_verts[i].y
- for i in t_right:
- t_verts[i] = t_verts[i].copy()
- t_verts[i].z += dz * t_verts[i].y
-
- for k in range(n_x):
- lM = tM @ Matrix([
- [1, 0, 0, x0 + k * dx],
- [0, -1, 0, 0],
- [0, 0, 1, 0],
- [0, 0, 0, 1]
- ])
- f = len(verts)
-
- verts.extend([lM @ p for p in t_verts])
- faces.extend([tuple(i + f for i in p) for p in t_faces])
- matids.extend(t_idmats)
- uvs.extend(t_uvs)
-
- elif s.type == 'LINK_VALLEY':
- if d.valley_enable:
- f = len(verts)
- s0 = s.offset(-2 * d.tile_couloir)
- s1 = pan.last_seg(i)
- s2 = pan.next_seg(i)
- p0 = s0.p0
- p1 = s0.p1
- res, p, t = s0.intersect(s1)
- if res:
- p0 = p
- res, p, t = s0.intersect(s2)
- if res:
- p1 = p
- alt = self.z + d.valley_altitude
- x0, y0 = s1.p1
- x1, y1 = p0
- x2, y2 = p1
- x3, y3 = s2.p0
- z0 = alt + pan.altitude(s1.p1)
- z1 = alt + pan.altitude(p0)
- z2 = alt + pan.altitude(p1)
- z3 = alt + pan.altitude(s2.p0)
-
- verts.extend([
- (x0, y0, z0),
- (x1, y1, z1),
- (x2, y2, z2),
- (x3, y3, z3),
- ])
- faces.extend([
- (f, f + 3, f + 2, f + 1)
- ])
- matids.extend([
- idmat_valley
- ])
- uvs.extend([
- [(0, 0), (1, 0), (1, 1), (0, 1)]
- ])
-
- elif s.type == 'AXIS' and d.hip_enable and pan.side == 'LEFT':
-
- tmin = 0
- tmax = 1
- s0 = pan.last_seg(i)
- if s0.type == 'SIDE' and s.length > 0:
- tmin = 0 - d.tile_side / s.length
- s1 = pan.next_seg(i)
-
- if s1.type == 'SIDE' and s.length > 0:
- tmax = 1 + d.tile_side / s.length
-
- # print("tmin:%s tmax:%s" % (tmin, tmax))
- ####################
- # Faitiere
- ####################
-
- f = len(verts)
- s_len = (tmax - tmin) * s.length
- n_obj = 1 + int(s_len / d.hip_space_x)
- dx = s_len / n_obj
- x0 = 0.5 * dx
- v = s.v.normalized()
- p0 = s.lerp(tmin)
- tM = Matrix([
- [v.x, v.y, 0, p0.x],
- [v.y, -v.x, 0, p0.y],
- [0, 0, 1, self.z + d.hip_alt],
- [0, 0, 0, 1]
- ])
- t_verts = [p.copy() for p in t_pts]
-
- # apply slope
- for i in t_left:
- t_verts[i].z += t_verts[i].y * (pan.other_side.slope - d.tile_size_z / d.tile_size_y)
- for i in t_right:
- t_verts[i].z -= t_verts[i].y * (pan.slope - d.tile_size_z / d.tile_size_y)
-
- for k in range(n_obj):
- lM = tM @ Matrix([
- [1, 0, 0, x0 + k * dx],
- [0, -1, 0, 0],
- [0, 0, 1, 0],
- [0, 0, 0, 1]
- ])
- v = len(verts)
- verts.extend([lM @ p for p in t_verts])
- faces.extend([tuple(i + v for i in f) for f in t_faces])
- matids.extend(t_idmats)
- uvs.extend(t_uvs)
-
- def make_hole(self, context, hole_obj, o, d, update_parent=False):
- """
- Hole for t child on parent
- create / update a RoofCutter on parent
- assume context object is child roof
- with parent set
- """
- # print("Make hole :%s hole_obj:%s" % (o.name, hole_obj))
- if o.parent is None:
- return
- # root is a RoofSegment
- root = self.nodes[0].root
- r_pan = root.right
- l_pan = root.left
-
- # merge :
- # 5 ____________ 4
- # / |
- # / left |
- # /_____axis_____| 3 <- kill axis and this one
- # 0\ |
- # \ right |
- # 1 \____________| 2
- #
- # degenerate case:
- #
- # /|
- # / |
- # \ |
- # \|
- #
-
- segs = []
- last = len(r_pan.segs) - 1
- for i, seg in enumerate(r_pan.segs):
- # r_pan start parent roof side
- if i == last:
- to_merge = seg.copy
- elif seg.type != 'AXIS':
- segs.append(seg.copy)
-
- for i, seg in enumerate(l_pan.segs):
- # l_pan end parent roof side
- if i == 1:
- # 0 is axis
- to_merge.p1 = seg.p1
- segs.append(to_merge)
- elif seg.type != 'AXIS':
- segs.append(seg.copy)
-
- # if there is side offset:
- # create an arrow
- #
- # 4 s4
- # /|
- # / |___s1_______
- # / p3 | p2 s3
- # 0\ p0___s0_______| p1
- # \ |
- # 1 \|
- s0 = root.left._axis.offset(
- max(0.001,
- min(
- root.right.ysize - 0.001,
- root.right.ysize - d.hole_offset_right
- )
- ))
- s1 = root.left._axis.offset(
- -max(0.001,
- min(
- root.left.ysize - 0.001,
- root.left.ysize - d.hole_offset_left
- )
- ))
-
- s3 = segs[2].offset(
- -min(root.left.xsize - 0.001, d.hole_offset_front)
- )
- s4 = segs[0].copy
- p1 = s4.p1
- s4.p1 = segs[-1].p0
- s4.p0 = p1
- res, p0, t = s4.intersect(s0)
- res, p1, t = s0.intersect(s3)
- res, p2, t = s1.intersect(s3)
- res, p3, t = s4.intersect(s1)
- pts = []
- # pts in cw order for 'DIFFERENCE' mode
- pts.extend([segs[-1].p1, segs[-1].p0])
- if (segs[-1].p0 - p3).length > 0.001:
- pts.append(p3)
- pts.extend([p2, p1])
- if (segs[0].p1 - p0).length > 0.001:
- pts.append(p0)
- pts.extend([segs[0].p1, segs[0].p0])
-
- pts = [p.to_3d() for p in pts]
-
- if hole_obj is None:
- context.view_layer.objects.active = o.parent
- bpy.ops.archipack.roof_cutter(parent=d.t_parent, auto_manipulate=False)
- hole_obj = context.active_object
- else:
- context.view_layer.objects.active = hole_obj
-
- hole_obj.select_set(state=True)
- if d.parts[0].a0 < 0:
- y = -d.t_dist_y
- else:
- y = d.t_dist_y
-
- hole_obj.matrix_world = o.matrix_world @ Matrix([
- [1, 0, 0, 0],
- [0, 1, 0, y],
- [0, 0, 1, 0],
- [0, 0, 0, 1]
- ])
-
- hd = archipack_roof_cutter.datablock(hole_obj)
- hd.boundary = o.name
- hd.update_points(context, hole_obj, pts, update_parent=update_parent)
- hole_obj.select_set(state=False)
-
- context.view_layer.objects.active = o
-
- def change_coordsys(self, fromTM, toTM):
- """
- move shape fromTM into toTM coordsys
- """
- dp = (toTM.inverted() @ fromTM.translation).to_2d()
- da = toTM.row[1].to_2d().angle_signed(fromTM.row[1].to_2d())
- ca = cos(da)
- sa = sin(da)
- rM = Matrix([
- [ca, -sa],
- [sa, ca]
- ])
- for s in self.segs:
- tp = (rM @ s.p0) - s.p0 + dp
- s.rotate(da)
- s.translate(tp)
-
- def t_partition(self, array, begin, end):
- pivot = begin
- for i in range(begin + 1, end + 1):
- # wall idx
- if array[i][0] < array[begin][0]:
- pivot += 1
- array[i], array[pivot] = array[pivot], array[i]
- array[pivot], array[begin] = array[begin], array[pivot]
- return pivot
-
- def sort_t(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.t_partition(array, begin, end)
- _quicksort(array, begin, pivot - 1)
- _quicksort(array, pivot + 1, end)
- return _quicksort(array, begin, end)
-
- def make_wall_fit(self, context, o, wall, inside):
- wd = wall.data.archipack_wall2[0]
- wg = wd.get_generator()
- z0 = self.z - wd.z
-
- # wg in roof coordsys
- wg.change_coordsys(wall.matrix_world, o.matrix_world)
-
- if inside:
- # fit inside
- offset = -0.5 * (1 - wd.x_offset) * wd.width
- else:
- # fit outside
- offset = 0
-
- wg.set_offset(offset)
-
- wall_t = [[] for w in wg.segs]
-
- for pan in self.pans:
- # walls segment
- for widx, wseg in enumerate(wg.segs):
-
- ls = wseg.line.length
-
- for seg in pan.segs:
- # intersect with a roof segment
- # any linked or axis intersection here
- # will be dup as they are between 2 roof parts
- res, p, t, v = wseg.line.intersect_ext(seg)
- if res:
- z = z0 + pan.altitude(p)
- wall_t[widx].append((t, z, t * ls))
-
- # lie under roof
- if type(wseg).__name__ == "CurvedWall":
- for step in range(12):
- t = step / 12
- p = wseg.line.lerp(t)
- if pan.inside(p):
- z = z0 + pan.altitude(p)
- wall_t[widx].append((t, z, t * ls))
- else:
- if pan.inside(wseg.line.p0):
- z = z0 + pan.altitude(wseg.line.p0)
- wall_t[widx].append((0, z, 0))
-
- old = context.active_object
- old_sel = wall.select_get()
- wall.select_set(state=True)
- context.view_layer.objects.active = wall
-
- wd.auto_update = False
- # setup splits count and first split to 0
- for widx, seg in enumerate(wall_t):
- self.sort_t(seg)
- # print("seg: %s" % seg)
- for s in seg:
- t, z, d = s
- wd.parts[widx].n_splits = len(seg) + 1
- wd.parts[widx].z[0] = 0
- wd.parts[widx].t[0] = 0
- break
-
- # add splits, skip dups
- for widx, seg in enumerate(wall_t):
- t0 = 0
- last_d = -1
- sid = 1
- for s in seg:
- t, z, d = s
- if t == 0:
- # add at end of last segment
- if widx > 0:
- lid = wd.parts[widx - 1].n_splits - 1
- wd.parts[widx - 1].z[lid] = z
- wd.parts[widx - 1].t[lid] = 1
- else:
- wd.parts[widx].z[0] = z
- wd.parts[widx].t[0] = t
- sid = 1
- else:
- if d - last_d < 0.001:
- wd.parts[widx].n_splits -= 1
- continue
- wd.parts[widx].z[sid] = z
- wd.parts[widx].t[sid] = t - t0
- t0 = t
- sid += 1
- last_d = d
-
- if wd.closed:
- last = wd.parts[wd.n_parts].n_splits - 1
- wd.parts[wd.n_parts].z[last] = wd.parts[0].z[0]
- wd.parts[wd.n_parts].t[last] = 1.0
-
- wd.auto_update = True
- """
- for s in self.segs:
- s.as_curve(context)
- for s in wg.segs:
- s.as_curve(context)
- """
- wall.select_set(state=old_sel)
- context.view_layer.objects.active = old
-
- def boundary(self, context, o):
- """
- either external or holes cuts
- """
- to_remove = []
- for b in o.children:
- d = archipack_roof_cutter.datablock(b)
- if d is not None:
- g = d.ensure_direction()
- g.change_coordsys(b.matrix_world, o.matrix_world)
- for i, pan in enumerate(self.pans):
- keep = pan.slice(g)
- if not keep:
- if i not in to_remove:
- to_remove.append(i)
- pan.limits()
- to_remove.sort()
- for i in reversed(to_remove):
- self.pans.pop(i)
-
- def draft(self, context, verts, edges):
- for pan in self.pans:
- pan.draw(context, self.z, verts, edges)
-
- for s in self.segs:
- if s.constraint_type == 'SLOPE':
- f = len(verts)
- p0 = s.p0.to_3d()
- p0.z = self.z
- p1 = s.p1.to_3d()
- p1.z = self.z
- verts.extend([p0, p1])
- edges.append([f, f + 1])
-
-
-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_parent(self, context):
-
- # update part a0
- o = context.active_object
- p, d = self.find_parent(context)
-
- if d is not None:
-
- o.parent = p
-
- # trigger object update
- # hole creation and parent's update
-
- self.parts[0].a0 = pi / 2
-
- elif self.t_parent != "":
- self.t_parent = ""
-
-
-def update_cutter(self, context):
- self.update(context, update_hole=True)
-
-
-def update_childs(self, context):
- self.update(context, update_childs=True, update_hole=True)
-
-
-def update_components(self, context):
- self.update(context, update_parent=False, update_hole=False)
-
-
-class ArchipackSegment():
- length : FloatProperty(
- name="Length",
- min=0.01,
- max=1000.0,
- default=4.0,
- update=update
- )
- a0 : FloatProperty(
- name="Angle",
- min=-2 * pi,
- max=2 * pi,
- default=0,
- subtype='ANGLE', unit='ROTATION',
- update=update_cutter
- )
- manipulators : CollectionProperty(type=archipack_manipulator)
-
-
-class ArchipackLines():
- n_parts : IntProperty(
- name="Parts",
- min=1,
- default=1, update=update_manipulators
- )
- # UI layout related
- parts_expand : BoolProperty(
- default=False
- )
-
- def draw(self, layout, context):
- box = layout.box()
- row = box.row()
- if self.parts_expand:
- row.prop(self, 'parts_expand', icon="TRIA_DOWN", text="Parts", emboss=False)
- box.prop(self, 'n_parts')
- for i, part in enumerate(self.parts):
- part.draw(layout, context, i)
- else:
- row.prop(self, 'parts_expand', icon="TRIA_RIGHT", text="Parts", emboss=False)
-
- def update_parts(self):
- # print("update_parts")
- # remove rows
- # NOTE:
- # n_parts+1
- # as last one is end point of last segment or closing one
- for i in range(len(self.parts), self.n_parts + 1, -1):
- self.parts.remove(i - 1)
-
- # add rows
- for i in range(len(self.parts), self.n_parts + 1):
- self.parts.add()
-
- self.setup_manipulators()
-
- def setup_parts_manipulators(self):
- 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)
- if n_manips < 5:
- s = p.manipulators.add()
- s.type_key = "SIZE"
- s.prop1_name = "offset"
- p.manipulators[2].prop1_name = str(i)
- p.manipulators[3].prop1_name = str(i + 1)
-
-
-class archipack_roof_segment(ArchipackSegment, PropertyGroup):
-
- bound_idx : IntProperty(
- name="Link to",
- default=0,
- min=0,
- update=update_manipulators
- )
- width_left : FloatProperty(
- name="L Width",
- min=0.01,
- default=3.0,
- update=update_cutter
- )
- width_right : FloatProperty(
- name="R Width",
- min=0.01,
- default=3.0,
- update=update_cutter
- )
- slope_left : FloatProperty(
- name="L slope",
- min=0.0,
- default=0.3,
- update=update_cutter
- )
- slope_right : FloatProperty(
- name="R slope",
- min=0.0,
- default=0.3,
- update=update_cutter
- )
- auto_left : EnumProperty(
- description="Left mode",
- name="Left",
- items=(
- ('AUTO', 'Auto', '', 0),
- ('WIDTH', 'Width', '', 1),
- ('SLOPE', 'Slope', '', 2),
- ('ALL', 'All', '', 3),
- ),
- default="AUTO",
- update=update_manipulators
- )
- auto_right : EnumProperty(
- description="Right mode",
- name="Right",
- items=(
- ('AUTO', 'Auto', '', 0),
- ('WIDTH', 'Width', '', 1),
- ('SLOPE', 'Slope', '', 2),
- ('ALL', 'All', '', 3),
- ),
- default="AUTO",
- update=update_manipulators
- )
- triangular_end : BoolProperty(
- name="Triangular end",
- default=False,
- update=update
- )
- take_precedence : BoolProperty(
- name="Take precedence",
- description="On T segment take width precedence",
- default=False,
- update=update
- )
-
- constraint_type : EnumProperty(
- items=(
- ('HORIZONTAL', 'Horizontal', '', 0),
- ('SLOPE', 'Slope', '', 1)
- ),
- default='HORIZONTAL',
- update=update_manipulators
- )
-
- enforce_part : EnumProperty(
- name="Enforce part",
- items=(
- ('AUTO', 'Auto', '', 0),
- ('VALLEY', 'Valley', '', 1),
- ('HIP', 'Hip', '', 2)
- ),
- default='AUTO',
- update=update
- )
-
- def find_in_selection(self, context):
- """
- find witch selected object this instance belongs to
- provide support for "copy to selected"
- """
- selected = context.selected_objects[:]
- for o in selected:
- d = archipack_roof.datablock(o)
- if d:
- for part in d.parts:
- if part == self:
- return d
- return None
-
- def draw(self, layout, context, index):
- box = layout.box()
- if index > 0:
- box.prop(self, "constraint_type", text=str(index + 1))
- if self.constraint_type == 'SLOPE':
- box.prop(self, "enforce_part", text="")
- else:
- box.label(text="Part 1:")
- box.prop(self, "length")
- box.prop(self, "a0")
-
- if index > 0:
- box.prop(self, 'bound_idx')
- if self.constraint_type == 'HORIZONTAL':
- box.prop(self, "triangular_end")
- row = box.row(align=True)
- row.prop(self, "auto_left", text="")
- row.prop(self, "auto_right", text="")
- if self.auto_left in {'ALL', 'WIDTH'}:
- box.prop(self, "width_left")
- if self.auto_left in {'ALL', 'SLOPE'}:
- box.prop(self, "slope_left")
- if self.auto_right in {'ALL', 'WIDTH'}:
- box.prop(self, "width_right")
- if self.auto_right in {'ALL', 'SLOPE'}:
- box.prop(self, "slope_right")
- elif self.constraint_type == 'HORIZONTAL':
- box.prop(self, "triangular_end")
-
- def update(self, context, manipulable_refresh=False, update_hole=False):
- props = self.find_in_selection(context)
- if props is not None:
- props.update(context,
- manipulable_refresh,
- update_parent=True,
- update_hole=True,
- update_childs=True)
-
-
-class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup):
- parts : CollectionProperty(type=archipack_roof_segment)
- z : FloatProperty(
- name="Altitude",
- default=3, precision=2, step=1,
- unit='LENGTH', subtype='DISTANCE',
- update=update_childs
- )
- slope_left : FloatProperty(
- name="L slope",
- default=0.5, precision=2, step=1,
- update=update_childs
- )
- slope_right : FloatProperty(
- name="R slope",
- default=0.5, precision=2, step=1,
- update=update_childs
- )
- width_left : FloatProperty(
- name="L width",
- default=3, precision=2, step=1,
- unit='LENGTH', subtype='DISTANCE',
- update=update_cutter
- )
- width_right : FloatProperty(
- name="R width",
- default=3, precision=2, step=1,
- unit='LENGTH', subtype='DISTANCE',
- update=update_cutter
- )
- draft : BoolProperty(
- options={'SKIP_SAVE'},
- name="Draft mode",
- default=False,
- update=update_manipulators
- )
- auto_update : BoolProperty(
- options={'SKIP_SAVE'},
- default=True,
- update=update_manipulators
- )
- quick_edit : BoolProperty(
- options={'SKIP_SAVE'},
- name="Quick Edit",
- default=False
- )
-
- tile_enable : BoolProperty(
- name="Enable",
- default=True,
- update=update_components
- )
- tile_solidify : BoolProperty(
- name="Solidify",
- default=True,
- update=update_components
- )
- tile_height : FloatProperty(
- name="Height",
- description="Amount for solidify",
- min=0,
- default=0.02,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- tile_bevel : BoolProperty(
- name="Bevel",
- default=False,
- update=update_components
- )
- tile_bevel_amt : FloatProperty(
- name="Amount",
- description="Amount for bevel",
- min=0,
- default=0.02,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- tile_bevel_segs : IntProperty(
- name="Segs",
- description="Bevel Segs",
- min=1,
- default=2,
- update=update_components
- )
- tile_alternate : BoolProperty(
- name="Alternate",
- default=False,
- update=update_components
- )
- tile_offset : FloatProperty(
- name="Offset",
- description="Offset from start",
- min=0,
- max=100,
- subtype="PERCENTAGE",
- update=update_components
- )
- tile_altitude : FloatProperty(
- name="Altitude",
- description="Altitude from roof",
- default=0.1,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- tile_size_x : FloatProperty(
- name="Width",
- description="Size of tiles on x axis",
- min=0.01,
- default=0.2,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- tile_size_y : FloatProperty(
- name="Length",
- description="Size of tiles on y axis",
- min=0.01,
- default=0.3,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- tile_size_z : FloatProperty(
- name="Thickness",
- description="Size of tiles on z axis",
- min=0.0,
- default=0.02,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- tile_space_x : FloatProperty(
- name="Width",
- description="Space between tiles on x axis",
- min=0.01,
- default=0.2,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- tile_space_y : FloatProperty(
- name="Length",
- description="Space between tiles on y axis",
- min=0.01,
- default=0.3,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- tile_fit_x : BoolProperty(
- name="Fit x",
- description="Fit roof on x axis",
- default=True,
- update=update_components
- )
- tile_fit_y : BoolProperty(
- name="Fit y",
- description="Fit roof on y axis",
- default=True,
- update=update_components
- )
- tile_expand : BoolProperty(
- options={'SKIP_SAVE'},
- name="Tiles",
- description="Expand tiles panel",
- default=False
- )
- tile_model : EnumProperty(
- name="Model",
- items=(
- ('BRAAS1', 'Braas 1', '', 0),
- ('BRAAS2', 'Braas 2', '', 1),
- ('ETERNIT', 'Eternit', '', 2),
- ('LAUZE', 'Lauze', '', 3),
- ('ROMAN', 'Roman', '', 4),
- ('ROUND', 'Round', '', 5),
- ('PLACEHOLDER', 'Square', '', 6),
- ('ONDULEE', 'Ondule', '', 7),
- ('METAL', 'Metal', '', 8),
- # ('USER', 'User defined', '', 7)
- ),
- default="BRAAS2",
- update=update_components
- )
- tile_side : FloatProperty(
- name="Side",
- description="Space on side",
- default=0,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- tile_couloir : FloatProperty(
- name="Valley",
- description="Space between tiles on valley",
- min=0,
- default=0.05,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- tile_border : FloatProperty(
- name="Bottom",
- description="Tiles offset from bottom",
- default=0,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
-
- gutter_expand : BoolProperty(
- options={'SKIP_SAVE'},
- name="Gutter",
- description="Expand gutter panel",
- default=False
- )
- gutter_enable : BoolProperty(
- name="Enable",
- default=True,
- update=update_components
- )
- gutter_alt : FloatProperty(
- name="Altitude",
- description="altitude",
- default=0,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- gutter_width : FloatProperty(
- name="Width",
- description="Width",
- min=0.01,
- default=0.15,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- gutter_dist : FloatProperty(
- name="Spacing",
- description="Spacing",
- min=0,
- default=0.05,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- gutter_boudin : FloatProperty(
- name="Small width",
- description="Small width",
- min=0,
- default=0.015,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- gutter_segs : IntProperty(
- default=6,
- min=1,
- name="Segs",
- update=update_components
- )
-
- beam_expand : BoolProperty(
- options={'SKIP_SAVE'},
- name="Beam",
- description="Expand beam panel",
- default=False
- )
- beam_enable : BoolProperty(
- name="Ridge pole",
- default=True,
- update=update_components
- )
- beam_width : FloatProperty(
- name="Width",
- description="Width",
- min=0.01,
- default=0.2,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- beam_height : FloatProperty(
- name="Height",
- description="Height",
- min=0.01,
- default=0.35,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- beam_offset : FloatProperty(
- name="Offset",
- description="Distance from roof border",
- default=0.02,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- beam_alt : FloatProperty(
- name="Altitude",
- description="Altitude from roof",
- default=-0.15,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- beam_sec_enable : BoolProperty(
- name="Hip rafter",
- default=True,
- update=update_components
- )
- beam_sec_width : FloatProperty(
- name="Width",
- description="Width",
- min=0.01,
- default=0.15,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- beam_sec_height : FloatProperty(
- name="Height",
- description="Height",
- min=0.01,
- default=0.2,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- beam_sec_alt : FloatProperty(
- name="Altitude",
- description="Distance from roof",
- default=-0.1,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- rafter_enable : BoolProperty(
- name="Rafter",
- default=True,
- update=update_components
- )
- rafter_width : FloatProperty(
- name="Width",
- description="Width",
- min=0.01,
- default=0.1,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- rafter_height : FloatProperty(
- name="Height",
- description="Height",
- min=0.01,
- default=0.2,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- rafter_spacing : FloatProperty(
- name="Spacing",
- description="Spacing",
- min=0.1,
- default=0.7,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- rafter_start : FloatProperty(
- name="Offset",
- description="Spacing from roof border",
- min=0,
- default=0.1,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- rafter_alt : FloatProperty(
- name="Altitude",
- description="Altitude from roof",
- max=-0.0001,
- default=-0.001,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
-
- hip_enable : BoolProperty(
- name="Enable",
- default=True,
- update=update_components
- )
- hip_expand : BoolProperty(
- options={'SKIP_SAVE'},
- name="Hips",
- description="Expand hips panel",
- default=False
- )
- hip_alt : FloatProperty(
- name="Altitude",
- description="Hip altitude from roof",
- default=0.1,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- hip_space_x : FloatProperty(
- name="Spacing",
- description="Space between hips",
- min=0.01,
- default=0.4,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- hip_size_x : FloatProperty(
- name="Length",
- description="Length of hip",
- min=0.01,
- default=0.4,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- hip_size_y : FloatProperty(
- name="Width",
- description="Width of hip",
- min=0.01,
- default=0.15,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- hip_size_z : FloatProperty(
- name="Height",
- description="Height of hip",
- min=0.0,
- default=0.15,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- hip_model : EnumProperty(
- name="Model",
- items=(
- ('ROUND', 'Round', '', 0),
- ('ETERNIT', 'Eternit', '', 1),
- ('FLAT', 'Flat', '', 2)
- ),
- default="ROUND",
- update=update_components
- )
- valley_altitude : FloatProperty(
- name="Altitude",
- description="Valley altitude from roof",
- default=0.1,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- valley_enable : BoolProperty(
- name="Valley",
- default=True,
- update=update_components
- )
-
- fascia_enable : BoolProperty(
- name="Enable",
- description="Enable Fascia",
- default=True,
- update=update_components
- )
- fascia_expand : BoolProperty(
- options={'SKIP_SAVE'},
- name="Fascia",
- description="Expand fascia panel",
- default=False
- )
- fascia_height : FloatProperty(
- name="Height",
- description="Height",
- min=0.01,
- default=0.3,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- fascia_width : FloatProperty(
- name="Width",
- description="Width",
- min=0.01,
- default=0.02,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- fascia_offset : FloatProperty(
- name="Offset",
- description="Offset from roof border",
- default=0,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- fascia_altitude : FloatProperty(
- name="Altitude",
- description="Fascia altitude from roof",
- default=0.1,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
-
- bargeboard_enable : BoolProperty(
- name="Enable",
- description="Enable Bargeboard",
- default=True,
- update=update_components
- )
- bargeboard_expand : BoolProperty(
- options={'SKIP_SAVE'},
- name="Bargeboard",
- description="Expand Bargeboard panel",
- default=False
- )
- bargeboard_height : FloatProperty(
- name="Height",
- description="Height",
- min=0.01,
- default=0.3,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- bargeboard_width : FloatProperty(
- name="Width",
- description="Width",
- min=0.01,
- default=0.02,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- bargeboard_offset : FloatProperty(
- name="Offset",
- description="Offset from roof border",
- default=0.001,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
- bargeboard_altitude : FloatProperty(
- name="Altitude",
- description="Fascia altitude from roof",
- default=0.1,
- unit='LENGTH', subtype='DISTANCE',
- update=update_components
- )
-
- t_parent : StringProperty(
- name="Parent",
- default="",
- update=update_parent
- )
- t_part : IntProperty(
- name="Part",
- description="Parent part index",
- default=0,
- min=0,
- update=update_cutter
- )
- t_dist_x : FloatProperty(
- name="Dist x",
- description="Location on axis ",
- default=0,
- update=update_cutter
- )
- t_dist_y : FloatProperty(
- name="Dist y",
- description="Lateral distance from axis",
- min=0.0001,
- default=0.0001,
- update=update_cutter
- )
- z_parent: FloatProperty(
- description="Delta z of t child for grand childs",
- default=0
- )
- hole_offset_left : FloatProperty(
- name="Left",
- description="Left distance from border",
- min=0,
- default=0,
- update=update_cutter
- )
- hole_offset_right : FloatProperty(
- name="Right",
- description="Right distance from border",
- min=0,
- default=0,
- update=update_cutter
- )
- hole_offset_front : FloatProperty(
- name="Front",
- description="Front distance from border",
- default=0,
- update=update_cutter
- )
-
- def make_wall_fit(self, context, o, wall, inside=False):
- origin = Vector((0, 0, self.z))
- g = self.get_generator(origin)
- g.make_roof(context)
- g.make_wall_fit(context, o, wall, inside)
-
- def update_parts(self):
- # NOTE:
- # n_parts+1
- # as last one is end point of last segment or closing one
- for i in range(len(self.parts), self.n_parts, -1):
- self.parts.remove(i - 1)
-
- # add rows
- for i in range(len(self.parts), self.n_parts):
- bound_idx = len(self.parts)
- self.parts.add()
- self.parts[-1].bound_idx = bound_idx
-
- self.setup_manipulators()
-
- def setup_manipulators(self):
- if len(self.manipulators) < 1:
- s = self.manipulators.add()
- s.type_key = "SIZE"
- s.prop1_name = "z"
- s.normal = (0, 1, 0)
- if len(self.manipulators) < 2:
- s = self.manipulators.add()
- s.type_key = "SIZE"
- s.prop1_name = "width_left"
- if len(self.manipulators) < 3:
- s = self.manipulators.add()
- s.type_key = "SIZE"
- s.prop1_name = "width_right"
-
- 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 = 'DUMB_STRING'
- s.prop1_name = str(i + 1)
- p.manipulators[2].prop1_name = str(i + 1)
- if n_manips < 4:
- s = p.manipulators.add()
- s.type_key = 'SIZE'
- s.prop1_name = "width_left"
- if n_manips < 5:
- s = p.manipulators.add()
- s.type_key = 'SIZE'
- s.prop1_name = "width_right"
- if n_manips < 6:
- s = p.manipulators.add()
- s.type_key = 'SIZE'
- s.prop1_name = "slope_left"
- if n_manips < 7:
- s = p.manipulators.add()
- s.type_key = 'SIZE'
- s.prop1_name = "slope_right"
-
- def get_generator(self, origin=Vector((0, 0, 0))):
- g = RoofGenerator(self, origin)
-
- # TODO: sort part by bound idx so deps always find parent
-
- for i, part in enumerate(self.parts):
- # skip part if bound_idx > parent
- # so deps always see parent
- if part.bound_idx <= i:
- g.add_part(part)
- g.locate_manipulators()
- return g
-
- def make_surface(self, o, verts, edges):
- bm = bmesh.new()
- 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]]))
- bm.edges.ensure_lookup_table()
- # bmesh.ops.contextual_create(bm, geom=bm.edges)
- bm.to_mesh(o.data)
- bm.free()
-
- def find_parent(self, context):
- o = context.scene.objects.get(self.t_parent.strip())
- return o, archipack_roof.datablock(o)
-
- def intersection_angle(self, t_slope, t_width, p_slope, angle):
- # 2d intersection angle between two roofs parts
- dy = abs(t_slope * t_width / p_slope)
- ca = cos(angle)
- ta = tan(angle)
- if ta == 0:
- w0 = 0
- else:
- w0 = dy * ta
- if ca == 0:
- w1 = 0
- else:
- w1 = t_width / ca
- dx = w1 - w0
- return atan2(dy, dx)
-
- def relocate_child(self, context, o, g, child):
-
- d = archipack_roof.datablock(child)
-
- if d is not None and d.t_part - 1 < len(g.segs):
- # print("relocate_child(%s)" % (child.name))
-
- seg = g.segs[d.t_part]
- # adjust T part matrix_world from parent
- # T part origin located on parent axis
- # with y in parent direction
- t = (d.t_dist_x / seg.length)
- x, y, z = seg.lerp(t).to_3d()
- dy = -seg.v.normalized()
- child.matrix_world = o.matrix_world @ Matrix([
- [dy.x, -dy.y, 0, x],
- [dy.y, dy.x, 0, y],
- [0, 0, 1, z],
- [0, 0, 0, 1]
- ])
-
- def relocate_childs(self, context, o, g):
- for child in o.children:
- d = archipack_roof.datablock(child)
- if d is not None and d.t_parent == o.name:
- self.relocate_child(context, o, g, child)
-
- def update_childs(self, context, o, g):
- for child in o.children:
- d = archipack_roof.datablock(child)
- if d is not None and d.t_parent == o.name:
- # print("upate_childs(%s)" % (child.name))
- child.select_set(state=True)
- context.view_layer.objects.active = child
- # regenerate hole
- d.update(context, update_hole=True, update_parent=False)
- child.select_set(state=False)
- o.select_set(state=True)
- context.view_layer.objects.active = o
-
- def update(self,
- context,
- manipulable_refresh=False,
- update_childs=False,
- update_parent=True,
- update_hole=False,
- force_update=False):
- """
- update_hole: on t_child must update parent
- update_childs: force childs update
- force_update: skip throttle
- """
- # print("update")
- 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, edges, faces, matids, uvs = [], [], [], [], []
-
- y = 0
- z = self.z
- p, d = self.find_parent(context)
- g = None
-
- # t childs: use parent to relocate
- # setup slopes into generator
- if d is not None:
- pg = d.get_generator()
- pg.make_roof(context)
-
- if self.t_part - 1 < len(pg.segs):
-
- seg = pg.nodes[self.t_part].root
-
- d.relocate_child(context, p, pg, o)
-
- a0 = self.parts[0].a0
- a_axis = a0 - pi / 2
- a_offset = 0
- s_left = self.slope_left
- w_left = -self.width_left
- s_right = self.slope_right
- w_right = self.width_right
- if a0 > 0:
- # a_axis est mesure depuis la perpendiculaire à l'axe
- slope = seg.right.slope
- y = self.t_dist_y
- else:
- a_offset = pi
- slope = seg.left.slope
- y = -self.t_dist_y
- s_left, s_right = s_right, s_left
- w_left, w_right = -w_right, -w_left
-
- if slope == 0:
- slope = 0.0001
-
- # print("slope: %s" % (slope))
-
- z = d.z_parent + d.z - self.t_dist_y * slope
- self.z_parent = z - self.z
- # a_right from axis cross z
-
- b_right = self.intersection_angle(
- s_left,
- w_left,
- slope,
- a_axis)
-
- a_right = b_right + a_offset
-
- b_left = self.intersection_angle(
- s_right,
- w_right,
- slope,
- a_axis)
-
- a_left = b_left + a_offset
-
- g = self.get_generator(origin=Vector((0, y, z)))
-
- # override by user defined slope if any
- make_right = True
- make_left = True
- for s in g.segs:
- if (s.constraint_type == 'SLOPE' and
- s.v0_idx == 0):
- da = g.segs[0].v.angle_signed(s.v)
- if da > 0:
- make_left = False
- else:
- make_right = False
-
- if make_left:
- # Add 'SLOPE' constraints for segment 0
- v = Vector((cos(a_left), sin(a_left)))
- s = StraightRoof(g.origin, v)
- s.v0_idx = 0
- s.constraint_type = 'SLOPE'
- # s.enforce_part = 'VALLEY'
- s.angle_0 = a_left
- s.take_precedence = False
- g.segs.append(s)
-
- if make_right:
- v = Vector((cos(a_right), sin(a_right)))
- s = StraightRoof(g.origin, v)
- s.v0_idx = 0
- s.constraint_type = 'SLOPE'
- # s.enforce_part = 'VALLEY'
- s.angle_0 = a_right
- s.take_precedence = False
- g.segs.append(s)
-
- if g is None:
- g = self.get_generator(origin=Vector((0, y, z)))
-
- # setup per segment manipulators
- if len(g.segs) > 0:
- f = g.segs[0]
- # z
- n = f.straight(-1, 0).v.to_3d()
- self.manipulators[0].set_pts([(0, 0, 0), (0, 0, self.z), (1, 0, 0)], normal=n)
- # left width
- n = f.sized_normal(0, -self.width_left)
- self.manipulators[1].set_pts([n.p0.to_3d(), n.p1.to_3d(), (-1, 0, 0)])
- # right width
- n = f.sized_normal(0, self.width_right)
- self.manipulators[2].set_pts([n.p0.to_3d(), n.p1.to_3d(), (1, 0, 0)])
-
- g.make_roof(context)
-
- # update childs here so parent may use
- # new holes when parent shape does change
- if update_childs:
- self.update_childs(context, o, g)
-
- # on t_child
- if d is not None and update_hole:
- hole_obj = self.find_hole(context, o)
- g.make_hole(context, hole_obj, o, self, update_parent)
- # print("make_hole")
-
- # add cutters
- g.boundary(context, o)
-
- if self.draft:
-
- g.draft(context, verts, edges)
- g.gutter(self, verts, faces, edges, matids, uvs)
- self.make_surface(o, verts, edges)
-
- else:
-
- if self.bargeboard_enable:
- g.bargeboard(self, verts, faces, edges, matids, uvs)
-
- if self.fascia_enable:
- g.fascia(self, verts, faces, edges, matids, uvs)
-
- if self.beam_enable:
- g.beam_primary(self, verts, faces, edges, matids, uvs)
-
- g.hips(self, verts, faces, edges, matids, uvs)
-
- if self.gutter_enable:
- g.gutter(self, verts, faces, edges, matids, uvs)
-
- bmed.buildmesh(
-
- context, o, verts, faces, matids=matids, uvs=uvs,
- weld=False, clean=False, auto_smooth=True, temporary=False)
-
- # bpy.ops.object.mode_set(mode='EDIT')
- g.lambris(context, o, self)
- # print("lambris")
-
- if self.rafter_enable:
- # bpy.ops.object.mode_set(mode='EDIT')
- g.rafter(context, o, self)
- # print("rafter")
-
- if self.quick_edit and not force_update:
- if self.tile_enable:
- bpy.ops.archipack.roof_throttle_update(name=o.name)
- else:
- # throttle here
- if self.tile_enable:
- g.couverture(context, o, self)
- # print("couverture")
-
- # enable manipulators rebuild
- if manipulable_refresh:
- self.manipulable_refresh = True
- # print("rafter")
- # restore context
- self.restore_context(context)
- # print("restore context")
-
- def find_hole(self, context, o):
- p, d = self.find_parent(context)
- if d is not None:
- for child in p.children:
- cd = archipack_roof_cutter.datablock(child)
- if cd is not None and cd.boundary == o.name:
- return child
- return None
-
- 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 > 0:
- # start angle
- self.manip_stack.append(part.manipulators[0].setup(context, o, part))
-
- if part.constraint_type == 'HORIZONTAL':
- # length / radius + angle
- self.manip_stack.append(part.manipulators[1].setup(context, o, part))
-
- # index
- self.manip_stack.append(part.manipulators[2].setup(context, o, self))
-
- # size left
- if part.auto_left in {'WIDTH', 'ALL'}:
- self.manip_stack.append(part.manipulators[3].setup(context, o, part))
- # size right
- if part.auto_right in {'WIDTH', 'ALL'}:
- self.manip_stack.append(part.manipulators[4].setup(context, o, part))
- # slope left
- if part.auto_left in {'SLOPE', 'ALL'}:
- self.manip_stack.append(part.manipulators[5].setup(context, o, part))
- # slope right
- if part.auto_right in {'SLOPE', 'ALL'}:
- self.manip_stack.append(part.manipulators[6].setup(context, o, part))
-
- for m in self.manipulators:
- self.manip_stack.append(m.setup(context, o, self))
-
- def draw(self, layout, context):
- box = layout.box()
- row = box.row()
- if self.parts_expand:
- row.prop(self, 'parts_expand', icon="TRIA_DOWN", text="Parts", emboss=False)
- box.prop(self, 'n_parts')
- # box.prop(self, 'closed')
- for i, part in enumerate(self.parts):
- part.draw(layout, context, i)
- else:
- row.prop(self, 'parts_expand', icon="TRIA_RIGHT", text="Parts", emboss=False)
-
-
-def update_hole(self, context):
- # update parent's roof only when manipulated
- self.update(context, update_parent=True)
-
-
-def update_operation(self, context):
- self.reverse(context, make_ccw=(self.operation == 'INTERSECTION'))
-
-
-class archipack_roof_cutter_segment(ArchipackCutterPart, PropertyGroup):
- manipulators : CollectionProperty(type=archipack_manipulator)
- type : EnumProperty(
- name="Type",
- items=(
- ('SIDE', 'Side', 'Side with bargeboard', 0),
- ('BOTTOM', 'Bottom', 'Bottom with gutter', 1),
- ('LINK', 'Side link', 'Side without decoration', 2),
- ('AXIS', 'Top', 'Top part with hip and beam', 3)
- # ('LINK_VALLEY', 'Side valley', 'Side with valley', 3),
- # ('LINK_HIP', 'Side hip', 'Side with hip', 4)
- ),
- default='SIDE',
- update=update_hole
- )
-
- def find_in_selection(self, context):
- selected = context.selected_objects[:]
- for o in selected:
- d = archipack_roof_cutter.datablock(o)
- if d:
- for part in d.parts:
- if part == self:
- return d
- return None
-
-
-class archipack_roof_cutter(ArchipackCutter, ArchipackObject, Manipulable, PropertyGroup):
- # boundary
- parts : CollectionProperty(type=archipack_roof_cutter_segment)
- boundary : StringProperty(
- default="",
- name="Boundary",
- description="Boundary of t child to cut parent"
- )
-
- def update_points(self, context, o, pts, update_parent=False):
- """
- Create boundary from roof
- """
- self.auto_update = False
- self.manipulable_disable(context)
- self.from_points(pts)
- self.manipulable_refresh = True
- self.auto_update = True
- if update_parent:
- self.update_parent(context, o)
- # print("update_points")
-
- def update_parent(self, context, o):
-
- d = archipack_roof.datablock(o.parent)
- if d is not None:
- o.parent.select_set(state=True)
- context.view_layer.objects.active = o.parent
- d.update(context, update_childs=False, update_hole=False)
- o.parent.select_set(state=False)
- context.view_layer.objects.active = o
- # print("update_parent")
-
-
-class ARCHIPACK_PT_roof_cutter(Panel):
- bl_idname = "ARCHIPACK_PT_roof_cutter"
- bl_label = "Roof Cutter"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_category = 'Archipack'
-
- @classmethod
- def poll(cls, context):
- return archipack_roof_cutter.filter(context.active_object)
-
- def draw(self, context):
- prop = archipack_roof_cutter.datablock(context.active_object)
- if prop is None:
- return
- layout = self.layout
- scene = context.scene
- box = layout.box()
- if prop.boundary != "":
- box.label(text="Auto Cutter:")
- box.label(text=prop.boundary)
- else:
- box.operator('archipack.roof_cutter_manipulate', icon='VIEW_PAN')
- box.prop(prop, 'operation', text="")
- box = layout.box()
- box.label(text="From curve")
- box.prop_search(prop, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
- if prop.user_defined_path != "":
- box.prop(prop, 'user_defined_resolution')
- # box.prop(prop, 'x_offset')
- # box.prop(prop, 'angle_limit')
- """
- box.prop_search(prop, "boundary", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
- """
- prop.draw(layout, context)
-
-
-class ARCHIPACK_PT_roof(Panel):
- bl_idname = "ARCHIPACK_PT_roof"
- bl_label = "Roof"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_category = 'Archipack'
-
- @classmethod
- def poll(cls, context):
- return archipack_roof.filter(context.active_object)
-
- def draw(self, context):
- o = context.active_object
- prop = archipack_roof.datablock(o)
- if prop is None:
- return
- scene = context.scene
- layout = self.layout
- row = layout.row(align=True)
- row.operator('archipack.roof_manipulate', icon='VIEW_PAN')
-
- box = layout.box()
- row = box.row(align=True)
- row.operator("archipack.roof_preset_menu", text=bpy.types.ARCHIPACK_OT_roof_preset_menu.bl_label)
- row.operator("archipack.roof_preset", text="", icon='ADD')
- row.operator("archipack.roof_preset", text="", icon='REMOVE').remove_active = True
- box = layout.box()
- box.prop_search(prop, "t_parent", scene, "objects", text="Parent", icon='OBJECT_DATA')
- layout.operator('archipack.roof_cutter').parent = o.name
- p, d = prop.find_parent(context)
- if d is not None:
- box.prop(prop, 't_part')
- box.prop(prop, 't_dist_x')
- box.prop(prop, 't_dist_y')
- box.label(text="Hole")
- box.prop(prop, 'hole_offset_front')
- box.prop(prop, 'hole_offset_left')
- box.prop(prop, 'hole_offset_right')
- box = layout.box()
- box.prop(prop, 'quick_edit', icon="MOD_MULTIRES")
- box.prop(prop, 'draft')
- if d is None:
- box.prop(prop, 'z')
- box.prop(prop, 'slope_left')
- box.prop(prop, 'slope_right')
- box.prop(prop, 'width_left')
- box.prop(prop, 'width_right')
- # parts
- prop.draw(layout, context)
- # tiles
- box = layout.box()
- row = box.row(align=True)
- if prop.tile_expand:
- row.prop(prop, 'tile_expand', icon="TRIA_DOWN", text="Covering", emboss=False)
- else:
- row.prop(prop, 'tile_expand', icon="TRIA_RIGHT", text="Covering", emboss=False)
- row.prop(prop, 'tile_enable')
- if prop.tile_expand:
- box.prop(prop, 'tile_model', text="")
-
- box.prop(prop, 'tile_solidify', icon='MOD_SOLIDIFY')
- if prop.tile_solidify:
- box.prop(prop, 'tile_height')
- box.separator()
- box.prop(prop, 'tile_bevel', icon='MOD_BEVEL')
- if prop.tile_bevel:
- box.prop(prop, 'tile_bevel_amt')
- box.prop(prop, 'tile_bevel_segs')
- box.separator()
- box.label(text="Tile size")
- box.prop(prop, 'tile_size_x')
- box.prop(prop, 'tile_size_y')
- box.prop(prop, 'tile_size_z')
- box.prop(prop, 'tile_altitude')
-
- box.separator()
- box.label(text="Distribution")
- box.prop(prop, 'tile_alternate', icon='NLA')
- row = box.row(align=True)
- row.prop(prop, 'tile_fit_x', icon='ALIGN')
- row.prop(prop, 'tile_fit_y', icon='ALIGN')
- box.prop(prop, 'tile_offset')
-
- box.label(text="Spacing")
- box.prop(prop, 'tile_space_x')
- box.prop(prop, 'tile_space_y')
-
- box.separator() # hip
- box.label(text="Borders")
- box.prop(prop, 'tile_side')
- box.prop(prop, 'tile_couloir')
- box.prop(prop, 'tile_border')
-
- box = layout.box()
- row = box.row(align=True)
- if prop.hip_expand:
- row.prop(prop, 'hip_expand', icon="TRIA_DOWN", text="Hip", emboss=False)
- else:
- row.prop(prop, 'hip_expand', icon="TRIA_RIGHT", text="Hip", emboss=False)
- row.prop(prop, 'hip_enable')
- if prop.hip_expand:
- box.prop(prop, 'hip_model', text="")
- box.prop(prop, 'hip_size_x')
- box.prop(prop, 'hip_size_y')
- box.prop(prop, 'hip_size_z')
- box.prop(prop, 'hip_alt')
- box.prop(prop, 'hip_space_x')
- box.separator()
- box.prop(prop, 'valley_enable')
- box.prop(prop, 'valley_altitude')
-
- box = layout.box()
- row = box.row(align=True)
-
- if prop.beam_expand:
- row.prop(prop, 'beam_expand', icon="TRIA_DOWN", text="Beam", emboss=False)
- else:
- row.prop(prop, 'beam_expand', icon="TRIA_RIGHT", text="Beam", emboss=False)
- if prop.beam_expand:
- box.prop(prop, 'beam_enable')
- if prop.beam_enable:
- box.prop(prop, 'beam_width')
- box.prop(prop, 'beam_height')
- box.prop(prop, 'beam_offset')
- box.prop(prop, 'beam_alt')
- box.separator()
- box.prop(prop, 'beam_sec_enable')
- if prop.beam_sec_enable:
- box.prop(prop, 'beam_sec_width')
- box.prop(prop, 'beam_sec_height')
- box.prop(prop, 'beam_sec_alt')
- box.separator()
- box.prop(prop, 'rafter_enable')
- if prop.rafter_enable:
- box.prop(prop, 'rafter_height')
- box.prop(prop, 'rafter_width')
- box.prop(prop, 'rafter_spacing')
- box.prop(prop, 'rafter_start')
- box.prop(prop, 'rafter_alt')
-
- box = layout.box()
- row = box.row(align=True)
- if prop.gutter_expand:
- row.prop(prop, 'gutter_expand', icon="TRIA_DOWN", text="Gutter", emboss=False)
- else:
- row.prop(prop, 'gutter_expand', icon="TRIA_RIGHT", text="Gutter", emboss=False)
- row.prop(prop, 'gutter_enable')
- if prop.gutter_expand:
- box.prop(prop, 'gutter_alt')
- box.prop(prop, 'gutter_width')
- box.prop(prop, 'gutter_dist')
- box.prop(prop, 'gutter_boudin')
- box.prop(prop, 'gutter_segs')
-
- box = layout.box()
- row = box.row(align=True)
- if prop.fascia_expand:
- row.prop(prop, 'fascia_expand', icon="TRIA_DOWN", text="Fascia", emboss=False)
- else:
- row.prop(prop, 'fascia_expand', icon="TRIA_RIGHT", text="Fascia", emboss=False)
- row.prop(prop, 'fascia_enable')
- if prop.fascia_expand:
- box.prop(prop, 'fascia_altitude')
- box.prop(prop, 'fascia_width')
- box.prop(prop, 'fascia_height')
- box.prop(prop, 'fascia_offset')
-
- box = layout.box()
- row = box.row(align=True)
- if prop.bargeboard_expand:
- row.prop(prop, 'bargeboard_expand', icon="TRIA_DOWN", text="Bargeboard", emboss=False)
- else:
- row.prop(prop, 'bargeboard_expand', icon="TRIA_RIGHT", text="Bargeboard", emboss=False)
- row.prop(prop, 'bargeboard_enable')
- if prop.bargeboard_expand:
- box.prop(prop, 'bargeboard_altitude')
- box.prop(prop, 'bargeboard_width')
- box.prop(prop, 'bargeboard_height')
- box.prop(prop, 'bargeboard_offset')
-
- """
- box = layout.box()
- row.prop_search(prop, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
- box.prop(prop, 'user_defined_resolution')
- box.prop(prop, 'angle_limit')
- """
-
-
-# ------------------------------------------------------------------
-# Define operator class to create object
-# ------------------------------------------------------------------
-
-
-class ARCHIPACK_OT_roof(ArchipackCreateTool, Operator):
- bl_idname = "archipack.roof"
- bl_label = "Roof"
- bl_description = "Roof"
- bl_category = 'Archipack'
- bl_options = {'REGISTER', 'UNDO'}
-
- def create(self, context):
- m = bpy.data.meshes.new("Roof")
- o = bpy.data.objects.new("Roof", m)
- d = m.archipack_roof.add()
- # make manipulators selectable
- d.manipulable_selectable = True
- self.link_object_to_scene(context, o)
- o.select_set(state=True)
- context.view_layer.objects.active = o
- self.add_material(o)
-
- # disable progress bar when
- # background render thumbs
- if not self.auto_manipulate:
- d.quick_edit = False
-
- self.load_preset(d)
- 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_set(state=True)
- context.view_layer.objects.active = o
- self.manipulate()
- return {'FINISHED'}
- else:
- self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
- return {'CANCELLED'}
-
-
-class ARCHIPACK_OT_roof_cutter(ArchipackCreateTool, Operator):
- bl_idname = "archipack.roof_cutter"
- bl_label = "Roof Cutter"
- bl_description = "Roof Cutter"
- bl_category = 'Archipack'
- bl_options = {'REGISTER', 'UNDO'}
-
- parent : StringProperty("")
-
- def create(self, context):
- m = bpy.data.meshes.new("Roof Cutter")
- o = bpy.data.objects.new("Roof Cutter", m)
- d = m.archipack_roof_cutter.add()
- parent = context.scene.objects.get(self.parent.strip())
- if parent is not None:
- o.parent = parent
- bbox = parent.bound_box
- angle_90 = pi / 2
- x0, y0, z = bbox[0]
- x1, y1, z = bbox[6]
- x = 0.2 * (x1 - x0)
- y = 0.2 * (y1 - y0)
- o.matrix_world = parent.matrix_world @ Matrix([
- [1, 0, 0, -3 * x],
- [0, 1, 0, 0],
- [0, 0, 1, 0],
- [0, 0, 0, 1]
- ])
- p = d.parts.add()
- p.a0 = - angle_90
- p.length = y
- p = d.parts.add()
- p.a0 = angle_90
- p.length = x
- p = d.parts.add()
- p.a0 = angle_90
- p.length = y
- d.n_parts = 3
- # d.close = True
- pd = archipack_roof.datablock(parent)
- pd.boundary = o.name
- else:
- o.location = context.scene.cursor.location
- # make manipulators selectable
- d.manipulable_selectable = True
- self.link_object_to_scene(context, o)
- o.select_set(state=True)
- context.view_layer.objects.active = o
- self.add_material(o)
- self.load_preset(d)
- update_operation(d, context)
- return o
-
- # -----------------------------------------------------
- # Execute
- # -----------------------------------------------------
- def execute(self, context):
- if context.mode == "OBJECT":
- bpy.ops.object.select_all(action="DESELECT")
- o = self.create(context)
- o.select_set(state=True)
- context.view_layer.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_roof_from_curve(ArchipackCreateTool, Operator):
- bl_idname = "archipack.roof_from_curve"
- bl_label = "Roof curve"
- bl_description = "Create a roof 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 draw(self, context):
- layout = self.layout
- row = layout.row()
- row.label(text="Use Properties panel (N) to define parms", icon='INFO')
-
- def create(self, context):
- curve = context.active_object
- m = bpy.data.meshes.new("Roof")
- o = bpy.data.objects.new("Roof", m)
- d = m.archipack_roof.add()
- # make manipulators selectable
- d.manipulable_selectable = True
- d.user_defined_path = curve.name
- self.link_object_to_scene(context, o)
- o.select_set(state=True)
- context.view_layer.objects.active = o
- d.update_path(context)
-
- spline = curve.data.splines[0]
- 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]
- ])
- o.select_set(state=True)
- context.view_layer.objects.active = o
- return o
-
- # -----------------------------------------------------
- # Execute
- # -----------------------------------------------------
- def execute(self, context):
- if context.mode == "OBJECT":
- bpy.ops.object.select_all(action="DESELECT")
- self.create(context)
- if self.auto_manipulate:
- bpy.ops.archipack.roof_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_roof_manipulate(Operator):
- bl_idname = "archipack.roof_manipulate"
- bl_label = "Manipulate"
- bl_description = "Manipulate"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(self, context):
- return archipack_roof.filter(context.active_object)
-
- def invoke(self, context, event):
- d = archipack_roof.datablock(context.active_object)
- d.manipulable_invoke(context)
- return {'FINISHED'}
-
-
-class ARCHIPACK_OT_roof_cutter_manipulate(Operator):
- bl_idname = "archipack.roof_cutter_manipulate"
- bl_label = "Manipulate"
- bl_description = "Manipulate"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(self, context):
- return archipack_roof_cutter.filter(context.active_object)
-
- def invoke(self, context, event):
- d = archipack_roof_cutter.datablock(context.active_object)
- d.manipulable_invoke(context)
- return {'FINISHED'}
-
-
-# Update throttle
-class ArchipackThrottleHandler():
- """
- One modal runs for each object at time
- when call for 2nd one
- update timer so first one wait more
- and kill 2nd one
- """
- def __init__(self, context, delay):
- self._timer = None
- self.start = 0
- self.update_state = False
- self.delay = delay
-
- def start_timer(self, context):
- self.start = time.time()
- self._timer = context.window_manager.event_timer_add(self.delay, window=context.window)
-
- def stop_timer(self, context):
- if self._timer is not None:
- context.window_manager.event_timer_remove(self._timer)
- self._timer = None
-
- def execute(self, context):
- """
- refresh timer on execute
- return
- True if modal should run
- False on complete
- """
- if self._timer is None:
- self.update_state = False
- self.start_timer(context)
- return True
-
- # already a timer running
- self.stop_timer(context)
-
- # prevent race conditions when already in update mode
- if self.is_updating:
- return False
-
- self.start_timer(context)
- return False
-
- def modal(self, context, event):
- if event.type == 'TIMER' and not self.is_updating:
- if time.time() - self.start > self.delay:
- self.update_state = True
- self.stop_timer(context)
- return True
- return False
-
- @property
- def is_updating(self):
- return self.update_state
-
-
-throttle_handlers = {}
-throttle_delay = 1
-
-
-class ARCHIPACK_OT_roof_throttle_update(Operator):
- bl_idname = "archipack.roof_throttle_update"
- bl_label = "Update childs with a delay"
-
- name : StringProperty()
-
- def kill_handler(self, context, name):
- if name in throttle_handlers.keys():
- throttle_handlers[name].stop_timer(context)
- del throttle_handlers[self.name]
-
- def get_handler(self, context, delay):
- global throttle_handlers
- if self.name not in throttle_handlers.keys():
- throttle_handlers[self.name] = ArchipackThrottleHandler(context, delay)
- return throttle_handlers[self.name]
-
- def modal(self, context, event):
- global throttle_handlers
- if self.name in throttle_handlers.keys():
- if throttle_handlers[self.name].modal(context, event):
- act = context.active_object
- o = context.scene.objects.get(self.name.strip())
- # print("delay update of %s" % (self.name))
- if o is not None:
- selected = o.select_get()
- o.select_set(state=True)
- context.view_layer.objects.active = o
- d = o.data.archipack_roof[0]
- d.update(context,
- force_update=True,
- update_parent=False)
- # skip_parent_update=self.skip_parent_update)
- o.select_set(state=selected)
- context.view_layer.objects.active = act
- del throttle_handlers[self.name]
- return {'FINISHED'}
- else:
- return {'PASS_THROUGH'}
- else:
- return {'FINISHED'}
-
- def execute(self, context):
- global throttle_delay
- handler = self.get_handler(context, throttle_delay)
- if handler.execute(context):
- context.window_manager.modal_handler_add(self)
- return {'RUNNING_MODAL'}
- return {'FINISHED'}
-
-
-# ------------------------------------------------------------------
-# Define operator class to load / save presets
-# ------------------------------------------------------------------
-
-
-class ARCHIPACK_OT_roof_preset_menu(PresetMenuOperator, Operator):
- bl_description = "Show Roof presets"
- bl_idname = "archipack.roof_preset_menu"
- bl_label = "Roof Styles"
- preset_subdir = "archipack_roof"
-
-
-class ARCHIPACK_OT_roof_preset(ArchipackPreset, Operator):
- """Add a Roof Styles"""
- bl_idname = "archipack.roof_preset"
- bl_label = "Add Roof Style"
- preset_menu = "ARCHIPACK_OT_roof_preset_menu"
-
- @property
- def blacklist(self):
- return ['n_parts', 'parts', 'manipulators', 'user_defined_path', 'quick_edit', 'draft']
-
-
-def register():
- # bpy.utils.register_class(archipack_roof_material)
- bpy.utils.register_class(archipack_roof_cutter_segment)
- bpy.utils.register_class(archipack_roof_cutter)
- bpy.utils.register_class(ARCHIPACK_PT_roof_cutter)
- bpy.utils.register_class(ARCHIPACK_OT_roof_cutter)
- bpy.utils.register_class(ARCHIPACK_OT_roof_cutter_manipulate)
- Mesh.archipack_roof_cutter = CollectionProperty(type=archipack_roof_cutter)
- bpy.utils.register_class(archipack_roof_segment)
- bpy.utils.register_class(archipack_roof)
- Mesh.archipack_roof = CollectionProperty(type=archipack_roof)
- bpy.utils.register_class(ARCHIPACK_OT_roof_preset_menu)
- bpy.utils.register_class(ARCHIPACK_PT_roof)
- bpy.utils.register_class(ARCHIPACK_OT_roof)
- bpy.utils.register_class(ARCHIPACK_OT_roof_preset)
- bpy.utils.register_class(ARCHIPACK_OT_roof_manipulate)
- bpy.utils.register_class(ARCHIPACK_OT_roof_from_curve)
- bpy.utils.register_class(ARCHIPACK_OT_roof_throttle_update)
-
-
-def unregister():
- # bpy.utils.unregister_class(archipack_roof_material)
- bpy.utils.unregister_class(archipack_roof_cutter_segment)
- bpy.utils.unregister_class(archipack_roof_cutter)
- bpy.utils.unregister_class(ARCHIPACK_PT_roof_cutter)
- bpy.utils.unregister_class(ARCHIPACK_OT_roof_cutter)
- bpy.utils.unregister_class(ARCHIPACK_OT_roof_cutter_manipulate)
- del Mesh.archipack_roof_cutter
- bpy.utils.unregister_class(archipack_roof_segment)
- bpy.utils.unregister_class(archipack_roof)
- del Mesh.archipack_roof
- bpy.utils.unregister_class(ARCHIPACK_OT_roof_preset_menu)
- bpy.utils.unregister_class(ARCHIPACK_PT_roof)
- bpy.utils.unregister_class(ARCHIPACK_OT_roof)
- bpy.utils.unregister_class(ARCHIPACK_OT_roof_preset)
- bpy.utils.unregister_class(ARCHIPACK_OT_roof_manipulate)
- bpy.utils.unregister_class(ARCHIPACK_OT_roof_from_curve)
- bpy.utils.unregister_class(ARCHIPACK_OT_roof_throttle_update)
diff --git a/archipack/archipack_slab.py b/archipack/archipack_slab.py
deleted file mode 100644
index ae1d7be4..00000000
--- a/archipack/archipack_slab.py
+++ /dev/null
@@ -1,1762 +0,0 @@
-# -*- 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
-from .archipack_cutter import (
- CutAblePolygon, CutAbleGenerator,
- ArchipackCutter,
- ArchipackCutterPart
- )
-
-
-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(CutAblePolygon, CutAbleGenerator):
-
- def __init__(self, parts):
- self.parts = parts
- self.segs = []
- self.holes = []
- self.convex = True
- self.xsize = 0
-
- 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:
- 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].line)
-
- 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, don't 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 s in self.segs:
- if "Curved" in type(s).__name__:
- for i in range(16):
- # x, y = slab.line.lerp(i / 16)
- verts.append(s.lerp(i / 16).to_3d())
- else:
- # x, y = s.line.p0
- verts.append(s.p0.to_3d())
- """
- 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 limits(self):
- x_size = [s.p0.x for s in self.segs]
- self.xsize = max(x_size) - min(x_size)
-
- def cut(self, context, o):
- """
- either external or holes cuts
- """
- self.limits()
-
- self.as_lines(step_angle=0.0502)
- # self.segs = [s.line for s in self.segs]
-
- for b in o.children:
- d = archipack_slab_cutter.datablock(b)
- if d is not None:
- g = d.ensure_direction()
- g.change_coordsys(b.matrix_world, o.matrix_world)
- self.slice(g)
-
- def slab(self, context, o, d):
-
- verts = []
- self.get_verts(verts)
- if len(verts) > 2:
-
- 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)
-
- self.cut_holes(bm, self)
-
- bmesh.ops.dissolve_limit(bm,
- angle_limit=0.01,
- use_dissolve_boundaries=False,
- verts=bm.verts,
- edges=bm.edges,
- delimit={'MATERIAL'})
-
- bm.to_mesh(o.data)
- bm.free()
- # geom = bm.faces[:]
- # verts = bm.verts[:]
- # bmesh.ops.solidify(bm, geom=geom, thickness=d.z)
-
- # merge with object
- # bmed.bmesh_join(context, o, [bm], normal_update=True)
-
- bpy.ops.object.mode_set(mode='OBJECT')
-
-
-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 = 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.strip())
- 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:
- if "C_" in self.type:
- p = Vector((0, 0))
- v = self.length * Vector((cos(self.a0), sin(self.a0)))
- w = StraightSlab(p, v)
- a0 = pi / 2
- else:
- c = -self.radius * Vector((cos(self.a0), sin(self.a0)))
- w = CurvedSlab(c, self.radius, self.a0, pi)
-
- # w0 - w - w1
- if idx + 1 == d.n_parts:
- dp = - w.p0
- else:
- 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()
- box.prop(self, "type", text=str(index + 1))
- self.draw_insert(context, box, index)
- if self.type in ['C_SEG']:
- box.prop(self, "radius")
- box.prop(self, "da")
- else:
- box.prop(self, "length")
- box.prop(self, "a0")
- # box.prop(self, "offset")
-
-
-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 = 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=True,
- name="Close",
- options={'SKIP_SAVE'},
- update=update_manipulators
- )
- # UI layout related
- parts_expand : BoolProperty(
- options={'SKIP_SAVE'},
- default=False
- )
-
- x_offset : FloatProperty(
- name="Offset",
- min=-1000, max=1000,
- default=0.0, precision=2, step=1,
- unit='LENGTH', subtype='DISTANCE',
- update=update
- )
- z : FloatProperty(
- name="Thickness",
- default=0.3, precision=2, step=1,
- unit='LENGTH', subtype='DISTANCE',
- update=update
- )
- auto_synch : BoolProperty(
- name="Auto-Synch",
- 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_set(state=True)
- c.data.archipack_fence[0].n_parts = 3
- c.select_set(state=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_set(state=True)
- context.view_layer.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) or (next_idx == 0 and i + 1 == self.n_parts):
- # shared: should move last point
- # and apply to next segments
- # this is overridden for common segs
- # but translate non common ones
- dp = seg.p1 - w.segs[idx].p1
- w.translate(idx, dp)
-
- # shared: transfer 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
- last_seg = None
- for i, seg in enumerate(w.segs):
-
- d.parts[i].a0 = seg.delta_angle(last_seg)
-
- last_seg = seg
-
- 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_set(state=True)
- context.view_layer.objects.active = wall
-
- d.auto_update = True
- wall.select_set(state=False)
-
- o.select_set(state=True)
- context.view_layer.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_set(state=True)
-
- # auto_update need object to be active to
- # setup manipulators on the right object
- context.view_layer.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_set(state=False)
-
- context.view_layer.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"
- p.manipulators[0].type_key = 'ANGLE'
- 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)
-
- # relocate before cutting segs
- self.relocate_childs(context, o, g)
-
- o.select_set(state=True)
- context.view_layer.objects.active = o
-
- g.cut(context, o)
-
- g.slab(context, o, self)
-
- 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())
-
- # 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
-
-
-def update_hole(self, context):
- # update parent's roof only when manipulated
- self.update(context, update_parent=True)
-
-
-def update_operation(self, context):
- self.reverse(context, make_ccw=(self.operation == 'INTERSECTION'))
-
-
-class archipack_slab_cutter_segment(ArchipackCutterPart, PropertyGroup):
- manipulators : CollectionProperty(type=archipack_manipulator)
- type : EnumProperty(
- name="Type",
- items=(
- ('DEFAULT', 'Side', 'Side with rake', 0),
- ('BOTTOM', 'Bottom', 'Bottom with gutter', 1),
- ('LINK', 'Side link', 'Side without decoration', 2),
- ('AXIS', 'Top', 'Top part with hip and beam', 3)
- # ('LINK_VALLEY', 'Side valley', 'Side with valley', 3),
- # ('LINK_HIP', 'Side hip', 'Side with hip', 4)
- ),
- default='DEFAULT',
- update=update_hole
- )
-
- def find_in_selection(self, context):
- selected = context.selected_objects[:]
- for o in selected:
- d = archipack_slab_cutter.datablock(o)
- if d:
- for part in d.parts:
- if part == self:
- return d
- return None
-
- def draw(self, layout, context, index):
- box = layout.box()
- box.label(text="Part:" + str(index + 1))
- # box.prop(self, "type", text=str(index + 1))
- box.prop(self, "length")
- box.prop(self, "a0")
-
-
-class archipack_slab_cutter(ArchipackCutter, ArchipackObject, Manipulable, PropertyGroup):
- parts : CollectionProperty(type=archipack_slab_cutter_segment)
-
- def update_points(self, context, o, pts, update_parent=False):
- self.auto_update = False
- self.from_points(pts)
- self.auto_update = True
- if update_parent:
- self.update_parent(context, o)
-
- def update_parent(self, context, o):
-
- d = archipack_slab.datablock(o.parent)
- if d is not None:
- o.parent.select_set(state=True)
- context.view_layer.objects.active = o.parent
- d.update(context)
- o.parent.select_set(state=False)
- context.view_layer.objects.active = o
-
-
-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):
- o = context.active_object
- prop = archipack_slab.datablock(o)
- if prop is None:
- return
- layout = self.layout
- layout.operator('archipack.slab_manipulate', icon='VIEW_PAN')
- box = layout.box()
- box.operator('archipack.slab_cutter').parent = o.name
- 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", 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", text="Parts", emboss=False)
-
-
-class ARCHIPACK_PT_slab_cutter(Panel):
- bl_idname = "ARCHIPACK_PT_slab_cutter"
- bl_label = "Slab Cutter"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_category = 'Archipack'
-
- @classmethod
- def poll(cls, context):
- return archipack_slab_cutter.filter(context.active_object)
-
- def draw(self, context):
- prop = archipack_slab_cutter.datablock(context.active_object)
- if prop is None:
- return
- layout = self.layout
- scene = context.scene
- box = layout.box()
- box.operator('archipack.slab_cutter_manipulate', icon='VIEW_PAN')
- box.prop(prop, 'operation', text="")
- box = layout.box()
- box.label(text="From curve")
- box.prop_search(prop, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
- if prop.user_defined_path != "":
- box.prop(prop, 'user_defined_resolution')
- # box.prop(prop, 'x_offset')
- # box.prop(prop, 'angle_limit')
- """
- box.prop_search(prop, "boundary", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
- """
- prop.draw(layout, context)
-
-
-# ------------------------------------------------------------------
-# 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
- self.link_object_to_scene(context, o)
- o.select_set(state=True)
- context.view_layer.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_set(state=True)
- context.view_layer.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(text="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.view_layer.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.Translation(pt)
- 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.view_layer.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.view_layer.objects.active = wall
- bpy.ops.archipack.reference_point()
- else:
- wall.parent.select_set(state=True)
- context.view_layer.objects.active = wall.parent
- wall.select_set(state=True)
- o.select_set(state=True)
- bpy.ops.archipack.parent_to_reference()
- wall.parent.select_set(state=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_set(state=True)
- context.view_layer.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'}
-
-
-class ARCHIPACK_OT_slab_cutter(ArchipackCreateTool, Operator):
- bl_idname = "archipack.slab_cutter"
- bl_label = "Slab Cutter"
- bl_description = "Slab Cutter"
- bl_category = 'Archipack'
- bl_options = {'REGISTER', 'UNDO'}
-
- parent : StringProperty("")
-
- def create(self, context):
- m = bpy.data.meshes.new("Slab Cutter")
- o = bpy.data.objects.new("Slab Cutter", m)
- d = m.archipack_slab_cutter.add()
- parent = context.scene.objects.get(self.parent.strip())
- if parent is not None:
- o.parent = parent
- bbox = parent.bound_box
- angle_90 = pi / 2
- x0, y0, z = bbox[0]
- x1, y1, z = bbox[6]
- x = 0.2 * (x1 - x0)
- y = 0.2 * (y1 - y0)
- o.matrix_world = parent.matrix_world @ Matrix([
- [1, 0, 0, -3 * x],
- [0, 1, 0, 0],
- [0, 0, 1, 0],
- [0, 0, 0, 1]
- ])
- p = d.parts.add()
- p.a0 = - angle_90
- p.length = y
- p = d.parts.add()
- p.a0 = angle_90
- p.length = x
- p = d.parts.add()
- p.a0 = angle_90
- p.length = y
- d.n_parts = 3
- # d.close = True
- pd = archipack_slab.datablock(parent)
- pd.boundary = o.name
- else:
- o.location = context.scene.cursor.location
- # make manipulators selectable
- d.manipulable_selectable = True
- self.link_object_to_scene(context, o)
- o.select_set(state=True)
- context.view_layer.objects.active = o
- # self.add_material(o)
- self.load_preset(d)
- update_operation(d, context)
- return o
-
- # -----------------------------------------------------
- # Execute
- # -----------------------------------------------------
- def execute(self, context):
- if context.mode == "OBJECT":
- bpy.ops.object.select_all(action="DESELECT")
- o = self.create(context)
- o.select_set(state=True)
- context.view_layer.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_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'}
-
-
-class ARCHIPACK_OT_slab_cutter_manipulate(Operator):
- bl_idname = "archipack.slab_cutter_manipulate"
- bl_label = "Manipulate"
- bl_description = "Manipulate"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(self, context):
- return archipack_slab_cutter.filter(context.active_object)
-
- def invoke(self, context, event):
- d = archipack_slab_cutter.datablock(context.active_object)
- d.manipulable_invoke(context)
- return {'FINISHED'}
-
-
-def register():
- bpy.utils.register_class(archipack_slab_cutter_segment)
- bpy.utils.register_class(archipack_slab_cutter)
- Mesh.archipack_slab_cutter = CollectionProperty(type=archipack_slab_cutter)
- bpy.utils.register_class(ARCHIPACK_OT_slab_cutter)
- bpy.utils.register_class(ARCHIPACK_PT_slab_cutter)
- bpy.utils.register_class(ARCHIPACK_OT_slab_cutter_manipulate)
-
- 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)
- del Mesh.archipack_slab_cutter
- bpy.utils.unregister_class(archipack_slab_cutter_segment)
- bpy.utils.unregister_class(archipack_slab_cutter)
- bpy.utils.unregister_class(ARCHIPACK_OT_slab_cutter)
- bpy.utils.unregister_class(ARCHIPACK_PT_slab_cutter)
- bpy.utils.unregister_class(ARCHIPACK_OT_slab_cutter_manipulate)
diff --git a/archipack/archipack_snap.py b/archipack/archipack_snap.py
deleted file mode 100644
index 9d50add7..00000000
--- a/archipack/archipack_snap.py
+++ /dev/null
@@ -1,347 +0,0 @@
-# -*- 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 {'RUNNING', '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
-import logging
-
-logger = logging.getLogger("archipack")
-
-
-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()
- placeloc = Vector()
- 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_elements = None
- snap_target = None
- pivot_point = None
- trans_orientation = None
-
-
-def snap_point(takeloc=None,
- draw=None,
- 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.copy()
- 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.copy()
-
- 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
- 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 = context.selected_objects[:]
- SnapStore.act = context.object
- bpy.ops.object.select_all(action="DESELECT")
- ts = context.tool_settings
- SnapStore.use_snap = ts.use_snap
- SnapStore.snap_elements = ts.snap_elements
- SnapStore.snap_target = ts.snap_target
- SnapStore.pivot_point = ts.transform_pivot_point
- SnapStore.trans_orientation = context.scene.transform_orientation_slots[0].type
- self.create_helper(context)
- # Use a timer to broadcast a TIMER event while transform.translate is running
- self._timer = context.window_manager.event_timer_add(0.1, window=context.window)
-
- if SnapStore.draw is not None:
- args = (self, context)
- self._draw_handler = bpy.types.SpaceView3D.draw_handler_add(SnapStore.draw, args, 'WINDOW', 'POST_PIXEL')
-
- def remove_timer(self, context):
- if self._timer is not None:
- context.window_manager.event_timer_remove(self._timer)
-
- def exit(self, context):
-
- self.remove_timer(context)
-
- if self._draw_handler is not None:
- bpy.types.SpaceView3D.draw_handler_remove(self._draw_handler, 'WINDOW')
-
- # Restore original context
- if hasattr(context, "tool_settings"):
- ts = context.tool_settings
- ts.use_snap = SnapStore.use_snap
- ts.snap_elements = SnapStore.snap_elements
- ts.snap_target = SnapStore.snap_target
- ts.transform_pivot_point = SnapStore.pivot_point
- context.scene.transform_orientation_slots[0].type = SnapStore.trans_orientation
- for o in SnapStore.sel:
- o.select_set(state=True)
- if SnapStore.act is not None:
- SnapStore.act.select_set(state=True)
- context.view_layer.objects.active = SnapStore.act
- self.destroy_helper(context)
- logger.debug("Snap.exit %s", context.object.name)
-
- 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 = bpy.data.objects.get('Archipack_snap_helper')
- if helper is not None:
- # print("helper found")
- if context.scene.objects.get('Archipack_snap_helper') is None:
- # print("link helper")
- # self.link_object_to_scene(context, helper)
- context.scene.collection.objects.link(helper)
- else:
- # print("create helper")
- m = bpy.data.meshes.new("Archipack_snap_helper")
- m.vertices.add(count=1)
- helper = bpy.data.objects.new("Archipack_snap_helper", m)
- context.scene.collection.objects.link(helper)
- helper.use_fake_user = True
- helper.data.use_fake_user = True
-
- helper.matrix_world = SnapStore.helper_matrix
- helper.select_set(state=True)
- context.view_layer.objects.active = helper
- SnapStore.helper = helper
-
- def destroy_helper(self, context):
- """
- Unlink helper
- currently only support OBJECT mode
- """
- if SnapStore.helper is not None:
- # @TODO: Fix this
- # self.unlink_object_from_scene(context, 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 = {'INTERNAL'} # , 'UNDO'
-
- def modal(self, context, event):
-
- if SnapStore.helper is not None:
- logger.debug("Snap.modal event %s %s location:%s",
- event.type,
- event.value,
- SnapStore.helper.location)
-
- context.area.tag_redraw()
-
- if event.type in ('TIMER', 'NOTHING'):
- SnapStore.callback(context, event, 'RUNNING', self)
- return {'PASS_THROUGH'}
-
- if event.type not in ('ESC', 'RIGHTMOUSE', 'LEFTMOUSE', 'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'):
- return {'PASS_THROUGH'}
-
- if event.type in ('ESC', 'RIGHTMOUSE'):
- SnapStore.callback(context, event, 'CANCEL', self)
- else:
- SnapStore.placeloc = SnapStore.helper.location
- # on tt modal exit with right click, the delta is 0 so exit
- if self.delta.length == 0:
- SnapStore.callback(context, event, 'CANCEL', self)
- else:
- 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':
-
- if event.type in ('ESC', 'RIGHTMOUSE'):
- return {'FINISHED'}
-
- self.init(context, event)
-
- logger.debug("Snap.invoke event %s %s location:%s act:%s",
- event.type,
- event.value,
- SnapStore.helper.location, context.object.name)
-
- context.window_manager.modal_handler_add(self)
-
- bpy.ops.transform.translate('INVOKE_DEFAULT',
- constraint_axis=SnapStore.constraint_axis,
- orient_type=SnapStore.transform_orientation,
- release_confirm=SnapStore.release_confirm)
-
- logger.debug("Snap.invoke transform.translate done")
-
- 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
deleted file mode 100644
index 9fbcdfe2..00000000
--- a/archipack/archipack_stair.py
+++ /dev/null
@@ -1,2845 +0,0 @@
-# -*- 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, tangent 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.dot(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.dot(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 = 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.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="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 = 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="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="Thickness",
- 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="Spacing",
- 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="#",
- 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.strip())
- 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.strip())
- 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 != '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='VIEW_PAN')
- 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='ADD')
- row.operator("archipack.stair_preset", text="", icon='REMOVE').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", 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", text="Parts", emboss=False)
-
- box = layout.box()
- row = box.row()
- if prop.steps_expand:
- row.prop(prop, 'steps_expand', icon="TRIA_DOWN", 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", text="Steps", emboss=False)
-
- box = layout.box()
- row = box.row(align=True)
- if prop.handrail_expand:
- row.prop(prop, 'handrail_expand', icon="TRIA_DOWN", text="Handrail", emboss=False)
- else:
- row.prop(prop, 'handrail_expand', icon="TRIA_RIGHT", 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", text="String", emboss=False)
- else:
- row.prop(prop, 'string_expand', icon="TRIA_RIGHT", 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", text="Post", emboss=False)
- else:
- row.prop(prop, 'post_expand', icon="TRIA_RIGHT", 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", text="Subs", emboss=False)
- else:
- row.prop(prop, 'subs_expand', icon="TRIA_RIGHT", 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", text="Panels", emboss=False)
- else:
- row.prop(prop, 'panel_expand', icon="TRIA_RIGHT", 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", text="Rails", emboss=False)
- else:
- row.prop(prop, 'rail_expand', icon="TRIA_RIGHT", 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", 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", 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()
- self.link_object_to_scene(context, o)
- o.select_set(state=True)
- context.view_layer.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_set(state=True)
- context.view_layer.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_description = "Show Stair Presets"
- 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']
-
-
-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_thumbs.py b/archipack/archipack_thumbs.py
deleted file mode 100644
index 730cc8a5..00000000
--- a/archipack/archipack_thumbs.py
+++ /dev/null
@@ -1,235 +0,0 @@
-# -*- 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 Asset-Flinguer
-# ----------------------------------------------------------
-import sys
-from mathutils import Vector
-import bpy
-
-
-def log(s):
- print("[log]" + s)
-
-
-def create_lamp(context, loc):
- bpy.ops.object.light_add(
- type='POINT',
- radius=1,
- align='WORLD',
- location=loc)
- lamp = context.active_object
- lamp.data.use_nodes = True
- lamp.location = loc
- tree = lamp.data.node_tree
- return tree, tree.nodes, lamp.data
-
-
-def create_camera(context, loc, rot):
- bpy.ops.object.camera_add(
- align='WORLD',
- enter_editmode=False,
- location=loc,
- rotation=rot)
- cam = context.active_object
- cam.location = loc
- cam.rotation_euler = rot
- context.scene.camera = cam
- return cam
-
-
-def get_center(o):
- x, y, z = o.bound_box[0]
- min_x = x
- min_y = y
- min_z = z
- x, y, z = o.bound_box[6]
- max_x = x
- max_y = y
- max_z = z
- return Vector((
- min_x + 0.5 * (max_x - min_x),
- min_y + 0.5 * (max_y - min_y),
- min_z + 0.5 * (max_z - min_z)))
-
-
-def apply_simple_material(o, name, color):
- m = bpy.data.materials.new(name)
- m.use_nodes = True
- for node in m.node_tree.nodes:
- if node.bl_idname == "ShaderNodeBsdfPrincipled":
- node.inputs[0].default_value = color
- break
- o.data.materials.append(m)
-
-
-def generateThumb(context, cls, preset, engine):
- log("### RENDER THUMB ############################")
-
- # Cleanup scene
- for o in context.scene.objects:
- o.select_set(state=True)
-
- bpy.ops.object.delete()
-
- log("Start generating: %s" % cls)
-
- # setup render
-
- context.scene.render.engine = engine
-
- if engine == 'CYCLES':
- cycles = context.scene.cycles
- cycles.progressive = 'PATH'
- cycles.samples = 24
- try:
- cycles.use_square_samples = True
- except:
- pass
- cycles.preview_samples = 24
- cycles.aa_samples = 24
- cycles.transparent_max_bounces = 8
- cycles.transparent_min_bounces = 8
- cycles.transmission_bounces = 8
- cycles.max_bounces = 8
- cycles.min_bounces = 6
- cycles.caustics_refractive = False
- cycles.caustics_reflective = False
- cycles.use_transparent_shadows = True
- cycles.diffuse_bounces = 1
- cycles.glossy_bounces = 4
-
- elif engine == 'BLENDER_EEVEE':
- eevee = context.scene.eevee
- eevee.use_gtao = True
- eevee.use_ssr = True
- eevee.use_soft_shadows = True
- eevee.taa_render_samples = 64
- else:
- raise RuntimeError("Unsupported render engine %s" % engine)
-
- render = context.scene.render
-
- # engine settings
- render.resolution_x = 150
- render.resolution_y = 100
- render.filepath = preset[:-3] + ".png"
-
- # create object, loading preset
- getattr(bpy.ops.archipack, cls)('INVOKE_DEFAULT', filepath=preset, auto_manipulate=False)
- o = context.active_object
- size = o.dimensions
- center = get_center(o)
-
- # opposite / tan (0.5 * fov) where fov is 49.134 deg
- dist = max(size) / 0.32
- loc = center + dist * Vector((-0.5, -1, 0.5)).normalized()
-
- log("Prepare camera")
- cam = create_camera(context, loc, (1.150952, 0.0, -0.462509))
- cam.data.lens = 50
-
- for ob in context.scene.objects:
- ob.select_set(state=False)
-
- o.select_set(state=True)
-
- bpy.ops.view3d.camera_to_view_selected()
- cam.data.lens = 45
-
- log("Prepare scene")
- # add plane
- bpy.ops.mesh.primitive_plane_add(
- size=1000,
- align='WORLD',
- enter_editmode=False,
- location=(0, 0, 0)
- )
-
- p = context.active_object
- apply_simple_material(p, "Plane", (1, 1, 1, 1))
-
- # add 3 lights
- tree, nodes, lamp = create_lamp(context, (3.69736, -7, 6.0))
- lamp.energy = 1000
- emit = nodes["Emission"]
- emit.inputs[1].default_value = 2000.0
-
- tree, nodes, lamp = create_lamp(context, (9.414563179016113, 5.446230888366699, 5.903861999511719))
- lamp.energy = 400
- emit = nodes["Emission"]
- falloff = nodes.new(type="ShaderNodeLightFalloff")
- falloff.inputs[0].default_value = 5
- tree.links.new(falloff.outputs[2], emit.inputs[1])
-
- tree, nodes, lamp = create_lamp(context, (-7.847615718841553, 1.03135085105896, 5.903861999511719))
- lamp.energy = 200
- emit = nodes["Emission"]
- falloff = nodes.new(type="ShaderNodeLightFalloff")
- falloff.inputs[0].default_value = 5
- tree.links.new(falloff.outputs[2], emit.inputs[1])
-
- # Set output filename.
- render.use_file_extension = True
- render.use_overwrite = True
- render.use_compositing = False
- render.use_sequencer = False
- render.resolution_percentage = 100
- # render.image_settings.file_format = 'PNG'
- # render.image_settings.color_mode = 'RGBA'
- # render.image_settings.color_depth = '8'
-
- # Configure output size.
- log("Render")
-
- # Render thumbnail
- bpy.ops.render.render(write_still=True)
-
- log("### COMPLETED ############################")
-
-
-if __name__ == "__main__":
-
- preset = ""
- engine = 'BLENDER_EEVEE' #'CYCLES'
- for arg in sys.argv:
- if arg.startswith("cls:"):
- cls = arg[4:]
- if arg.startswith("preset:"):
- preset = arg[7:]
- if arg.startswith("matlib:"):
- matlib = arg[7:]
- if arg.startswith("addon:"):
- module = arg[6:]
- if arg.startswith("engine:"):
- engine = arg[7:]
- try:
- # log("### ENABLE %s ADDON ############################" % module)
- bpy.ops.preferences.addon_enable(module=module)
- # log("### MATLIB PATH ############################")
- bpy.context.preferences.addons[module].preferences.matlib_path = matlib
- except:
- raise RuntimeError("module name not found")
- # log("### GENERATE ############################")
- generateThumb(bpy.context, cls, preset, engine)
diff --git a/archipack/archipack_truss.py b/archipack/archipack_truss.py
deleted file mode 100644
index 04cc0d96..00000000
--- a/archipack/archipack_truss.py
+++ /dev/null
@@ -1,380 +0,0 @@
-# -*- 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='VIEW_PAN')
- 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
- self.link_object_to_scene(context, o)
- o.select_set(state=True)
- context.view_layer.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_set(state=True)
- context.view_layer.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_wall2.py b/archipack/archipack_wall2.py
deleted file mode 100644
index 4469bc9a..00000000
--- a/archipack/archipack_wall2.py
+++ /dev/null
@@ -1,2437 +0,0 @@
-# -*- 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
-
-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, ArchipackDrawTool
-from .archipack_2d import Line, Arc
-from .archipack_snap import snap_point
-from .archipack_keymaps import Keymaps
-
-import logging
-logger = logging.getLogger("archipack")
-
-
-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 set_offset(self, offset, last=None):
- """
- Offset line and compute intersection point
- between segments
- """
- self.line = self.make_offset(offset, last)
-
- 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 make_hole(self, i, verts, z0):
- t = self.t_step[i]
- x, y = self.line.lerp(t)
- verts.append((x, y, z0))
-
- 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 set_offset(self, offset):
- last = None
- for i, seg in enumerate(self.segs):
- seg.set_offset(offset, last)
- last = seg.line
-
- if self.closed:
- w = self.segs[-1]
- if len(self.segs) > 1:
- w.line = w.make_offset(offset, self.segs[-2].line)
-
- p1 = self.segs[0].line.p1
- self.segs[0].line = self.segs[0].make_offset(offset, w.line)
- self.segs[0].line.p1 = p1
-
- 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 locate_manipulators(self, side):
-
- 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, don't 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)])
-
- 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):
-
- 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)
-
- self.locate_manipulators(side)
-
- 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 change_coordsys(self, fromTM, toTM):
- """
- move shape fromTM into toTM coordsys
- """
- dp = (toTM.inverted() @ fromTM.translation).to_2d()
- da = toTM.row[1].to_2d().angle_signed(fromTM.row[1].to_2d())
- ca = cos(da)
- sa = sin(da)
- rM = Matrix([
- [ca, -sa],
- [sa, ca]
- ])
- for s in self.segs:
- tp = (rM @ s.p0) - s.p0 + dp
- s.rotate(da)
- s.translate(tp)
-
- 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 make_surface(self, o, verts, height):
- 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)
- geom = bm.faces[:]
- bmesh.ops.solidify(bm, geom=geom, thickness=height)
- bm.to_mesh(o.data)
- bm.free()
-
- def make_hole(self, context, hole_obj, d):
-
- offset = -0.5 * (1 - d.x_offset) * d.width
-
- z0 = 0.1
- self.set_offset(offset)
-
- nb_segs = len(self.segs) - 1
- if d.closed:
- nb_segs += 1
-
- verts = []
- for i, wall in enumerate(self.segs):
- wall.param_t(d.step_angle)
- if i < nb_segs:
- for j in range(wall.n_step + 1):
- wall.make_hole(j, verts, -z0)
-
- self.make_surface(hole_obj, verts, d.z + z0)
-
-
-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.strip())
- 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 already
- # 5 o and w doesn't 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.view_layer.objects.active = o
- bpy.ops.archipack.reference_point()
- o.select_set(state=True)
- else:
- context.view_layer.objects.active = o.parent
- w.select_set(state=True)
- else:
- # w has parent
- if o.parent is not w.parent:
- link_to_parent = True
- context.view_layer.objects.active = w.parent
- o.select_set(state=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_set(state=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_set(state=True)
- context.view_layer.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:
- if "C_" in self.type:
- p = Vector((0, 0))
- v = self.length * Vector((cos(self.a0), sin(self.a0)))
- w = StraightWall(p, v, d.z, self.z, self.t, d.flip)
- a0 = pi / 2
- else:
- c = -self.radius * Vector((cos(self.a0), sin(self.a0)))
- w = CurvedWall(c, self.radius, self.a0, pi, d.z, self.z, self.t, d.flip)
-
- # w0 - w - w1
- if d.closed and idx == d.n_parts:
- dp = - w.p0
- else:
- 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",
- 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 = 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", text="Part " + str(index + 1), emboss=False)
- else:
- row.prop(self, 'expand', icon="TRIA_RIGHT", 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.strip())
- 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="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(
- name="Flip",
- 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="Real Time",
- 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()
-
- self.auto_update = False
-
- pts = [seg.p0.to_3d() for seg in g.segs]
-
- if not self.closed:
- g.segs.pop()
-
- g_segs = list(reversed(g.segs))
-
- last_seg = None
-
- for i, seg in enumerate(g_segs):
-
- s = seg.oposite
- if "Curved" in type(seg).__name__:
- self.parts[i].type = "C_WALL"
- self.parts[i].radius = s.r
- self.parts[i].da = s.da
- else:
- self.parts[i].type = "S_WALL"
- self.parts[i].length = s.length
-
- self.parts[i].a0 = s.delta_angle(last_seg)
-
- last_seg = s
-
- if self.closed:
- pts.append(pts[0])
-
- pts = list(reversed(pts))
-
- # 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_set(state=True)
- d.auto_update = False
- d.flip = child.flip
- d.y = self.width
- d.auto_update = True
- c.select_set(state=False)
- x, y = n.p - (0.5 * w * n.v)
- else:
- x, y = n.p - (child.pos.y * n.v)
-
- context.view_layer.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.view_layer.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):
- """
- Callback for t_parts childs
- """
- 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)
- # don't do anything ..
- # self.manipulable_release(context)
- # self.manipulate_mode = True
- self.manipulable_setup(context)
- self.manipulate_mode = True
-
- self._manipulable_invoke(context)
-
- return True
-
- def find_roof(self, context, o, g):
- tM = o.matrix_world
- up = Vector((0, 0, 1))
- for seg in g.segs:
- p = tM @ seg.p0.to_3d()
- p.z = 0.01
- # prevent self intersect
- o.hide_viewport = True
- res, pos, normal, face_index, r, matrix_world = context.scene.ray_cast(
- depsgraph=context.view_layer.depsgraph,
- origin=p,
- direction=up)
-
- o.hide_viewport = False
- # print("res:%s" % res)
- if res and r.data is not None and "archipack_roof" in r.data:
- return r, r.data.archipack_roof[0]
-
- return None, None
-
-
-# Update throttle (hack)
-# use 2 globals to store a timer and state of update_action
-# Use to update floor boolean on edit
-update_timer = None
-update_timer_updating = False
-throttle_delay = 0.5
-throttle_start = 0
-
-
-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:
- # can't rely on TIMER event as another timer may run
- if time.time() - throttle_start > throttle_delay:
- update_timer_updating = True
- o = context.scene.objects.get(self.name.strip())
- if o is not None:
- m = o.modifiers.get("AutoBoolean")
- if m is not None:
- o.hide_viewport = False
- # o.display_type = 'TEXTURED'
- # m.show_viewport = True
-
- return self.cancel(context)
- return {'PASS_THROUGH'}
-
- def execute(self, context):
- global update_timer
- global update_timer_updating
- global throttle_delay
- global throttle_start
- if update_timer is not None:
- context.window_manager.event_timer_remove(update_timer)
- if update_timer_updating:
- return {'CANCELLED'}
- # reset update_timer so it only occurs once 0.1s after last action
- throttle_start = time.time()
- update_timer = context.window_manager.event_timer_add(throttle_delay, context.window)
- return {'CANCELLED'}
- throttle_start = time.time()
- update_timer_updating = False
- context.window_manager.modal_handler_add(self)
- update_timer = context.window_manager.event_timer_add(throttle_delay, 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='VIEW_PAN')
- # 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')
- layout.operator("archipack.wall2_reverse", icon='FILE_REFRESH')
- row = layout.row(align=True)
- row.operator("archipack.wall2_fit_roof")
- # row.operator("archipack.wall2_fit_roof", text="Inside").inside = True
- 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
- self.link_object_to_scene(context, o)
- o.select_set(state=True)
- # around 12 degree
- m.auto_smooth_angle = 0.20944
- context.view_layer.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_set(state=True)
- context.view_layer.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.view_layer.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_set(state=True)
- context.view_layer.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.view_layer.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_set(state=True)
- context.view_layer.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.view_layer.objects.active = o
- bpy.ops.archipack.reference_point()
- else:
- o.parent.select_set(state=True)
- context.view_layer.objects.active = o.parent
- o.select_set(state=True)
- slab.select_set(state=True)
- bpy.ops.archipack.parent_to_reference()
- o.parent.select_set(state=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_set(state=True)
- context.view_layer.objects.active = o
- return {'FINISHED'}
- else:
- self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
- return {'CANCELLED'}
-
-
-class ARCHIPACK_OT_wall2_fit_roof(Operator):
- bl_idname = "archipack.wall2_fit_roof"
- bl_label = "Fit roof"
- bl_description = "Fit roof"
- bl_category = 'Archipack'
- bl_options = {'REGISTER', 'UNDO'}
-
- inside : BoolProperty(default=False)
-
- @classmethod
- def poll(self, context):
- return archipack_wall2.filter(context.active_object)
-
- def execute(self, context):
- o = context.active_object
- d = archipack_wall2.datablock(o)
- g = d.get_generator()
- r, rd = d.find_roof(context, o, g)
- if rd is not None:
- d.setup_childs(o, g)
- rd.make_wall_fit(context, r, o, self.inside)
- return {'FINISHED'}
-
-# ------------------------------------------------------------------
-# Define operator class to draw a wall
-# ------------------------------------------------------------------
-
-
-class ARCHIPACK_OT_wall2_draw(ArchipackDrawTool, Operator):
- bl_idname = "archipack.wall2_draw"
- bl_label = "Draw a Wall"
- bl_description = "Create a wall by drawing its baseline in 3D view"
- bl_category = 'Archipack'
-
- 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
-
- @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):
- logger.debug("ARCHIPACK_OT_wall2_draw.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.object
- if self.o is None:
- bpy.ops.archipack.wall2(auto_manipulate=False)
- o = context.object
- o.location = takeloc
- self.o = o
- d = archipack_wall2.datablock(o)
-
- part = d.parts[0]
- part.length = delta.length
- else:
- o = self.o
- # select and make active
- o.select_set(state=True)
- context.view_layer.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
- d.update(context)
-
- old.select_set(state=True)
- context.view_layer.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, width, y, z_offset = 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(axis=False)
- pts = [seg.p0 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()
- if event.type in {'NONE', 'TIMER', 'TIMER_REPORT', 'EVT_TWEAK_L', 'WINDOW_DEACTIVATE'}:
- return {'PASS_THROUGH'}
-
- if self.keymap.check(event, self.keymap.delete):
- self.feedback.disable()
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- self.o = None
- return {'FINISHED', 'PASS_THROUGH'}
-
- if self.state == 'STARTING' and event.type not in {'ESC', 'RIGHTMOUSE'}:
- # wait for takeloc being visible when button is over horizon
- takeloc = self.mouse_to_plane(context, event)
- if takeloc is not None:
- logger.debug("ARCHIPACK_OT_wall2_draw.modal(STARTING) location:%s", takeloc)
- snap_point(takeloc=takeloc,
- callback=self.sp_init,
- constraint_axis=(True, True, False),
- release_confirm=True)
- return {'RUNNING_MODAL'}
-
- elif self.state == 'RUNNING':
- # print("RUNNING")
- logger.debug("ARCHIPACK_OT_wall2_draw.modal(RUNNING) location:%s", self.takeloc)
- self.state = 'CREATE'
- snap_point(takeloc=self.takeloc,
- draw=self.sp_draw,
- takemat=self.takemat,
- transform_orientation=context.scene.transform_orientation_slots[0].type,
- 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 {'C', 'c'}:
-
- logger.debug("ARCHIPACK_OT_wall2_draw.modal(%s) C pressed", self.state)
- self.feedback.disable()
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
-
- o = self.o
- # select and make active
- o.select_set(state=True)
- context.view_layer.objects.active = o
-
- d = archipack_wall2.datablock(o)
- d.closed = True
-
- if bpy.ops.archipack.manipulate.poll():
- bpy.ops.archipack.manipulate('INVOKE_DEFAULT')
-
- return {'FINISHED'}
-
- 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", [
- ('ENTER', 'Add part'),
- ('BACK_SPACE', 'Remove part'),
- ('CTRL', 'Snap'),
- ('C', 'Close wall and exit'),
- ('MMBTN', 'Constraint to axis'),
- ('X Y', 'Constraint to axis'),
- ('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
-
- # select and make active
- o.select_set(state=True)
- context.view_layer.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_set(state=False)
- else:
- takemat = None
- takeloc = self.mouse_to_plane(context, event)
-
- if takeloc is not None:
- logger.debug("ARCHIPACK_OT_wall2_draw.modal(CREATE) location:%s", takeloc)
-
- 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
-
- # select and make active
- o.select_set(state=True)
- context.view_layer.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')
- logger.debug("ARCHIPACK_OT_wall2_draw.modal(CANCEL) %s", event.type)
- if self.o is None:
- for o in self.sel:
- o.select_set(state=True)
- # select and make active
- if self.act is not None:
- self.act.select_set(state=True)
- context.view_layer.objects.active = self.act
-
- else:
- o = self.o
- o.select_set(state=True)
- context.view_layer.objects.active = o
-
- # remove last segment with blender mode
- d = archipack_wall2.datablock(o)
- if not self.max_style_draw_tool:
- if not d.closed and d.n_parts > 1:
- d.n_parts -= 1
- o.select_set(state=True)
- context.view_layer.objects.active = o
- # make T child
- if self.parent is not None:
- d.t_part = self.parent
-
- if bpy.ops.archipack.manipulate.poll():
- bpy.ops.archipack.manipulate('INVOKE_DEFAULT', object_name=o.name)
-
- return {'FINISHED'}
-
- return {'PASS_THROUGH'}
-
- def invoke(self, context, event):
-
- if context.mode == "OBJECT":
- prefs = context.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 = 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_set(state=True)
- context.view_layer.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)
- bpy.utils.register_class(ARCHIPACK_OT_wall2_fit_roof)
-
-
-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)
- bpy.utils.unregister_class(ARCHIPACK_OT_wall2_fit_roof)
diff --git a/archipack/archipack_window.py b/archipack/archipack_window.py
deleted file mode 100644
index f9bb4903..00000000
--- a/archipack/archipack_window.py
+++ /dev/null
@@ -1,2267 +0,0 @@
-# -*- 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, Matrix
-from math import tan, sqrt
-from .bmesh_utils import BmeshEdit as bmed
-from .panel import Panel as WindowPanel
-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, ArchipackDrawTool, ArchipackCollectionManager
-from .archipack_keymaps import Keymaps
-
-
-def update(self, context):
- self.update(context)
-
-
-def update_childs(self, context):
- self.update(context, childs_only=True)
-
-
-def update_portal(self, context):
- self.update_portal(context)
-
-
-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 = 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
- )
- enable_glass : BoolProperty(
- name="Enable glass",
- default=True
- )
-
- @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
-
- side_cap_front = -1
- side_cap_back = -1
-
- if self.enable_glass:
- side_cap_front = 6
- side_cap_back = 7
-
- 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=side_cap_front,
- side_cap_back=side_cap_back # 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]
-
- side_cap_front = -1
- side_cap_back = -1
-
- if self.enable_glass:
- side_cap_front = 8
- side_cap_back = 9
-
- 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=side_cap_front,
- side_cap_back=side_cap_back # 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:
- self.unlink_object_from_scene(handle)
- bpy.data.objects.remove(handle, do_unlink=True)
-
- def update(self, context):
-
- o = self.find_in_selection(context)
-
- if o is None:
- return
-
- 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="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,
- )
- enable_glass : BoolProperty(
- name="Enable glass",
- default=True,
- update=update
- )
- warning : BoolProperty(
- options={'SKIP_SAVE'},
- 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
- )
- portal : BoolProperty(
- default=False,
- name="Portal",
- description="Generate a portal",
- 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 find_portal(self, o):
- for child in o.children:
- if child.type == 'LIGHT':
- return child
- return None
-
- def update_portal(self, context, o):
-
- lamp = self.find_portal(o)
- if self.portal:
- if lamp is None:
- bpy.ops.object.light_add(type='AREA')
- lamp = context.active_object
- lamp.name = "Portal"
- lamp.parent = o
-
- d = lamp.data
- d.cycles.is_portal = True
- d.shape = 'RECTANGLE'
- d.size = self.x
- d.size_y = self.z
-
- tM = Matrix([
- [1, 0, 0, 0],
- [0, 0, -1, -0.5 * self.y],
- [0, 1, 0, 0.5 * self.z + self.altitude],
- [0, 0, 0, 1]
- ])
- lamp.matrix_world = o.matrix_world @ tM
-
- elif lamp is not None:
- d = lamp.data
- self.unlink_object_from_scene(lamp)
- bpy.data.objects.remove(lamp)
- bpy.data.lights.remove(d)
-
- context.view_layer.objects.active = o
-
- 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)
- self.unlink_object_from_scene(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:
- self.unlink_object_from_scene(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_portal(self, context, o, linked, childs):
- # update portal
- dl = archipack_window.datablock(linked)
- dl.update_portal(context, linked)
-
- 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)
- self.unlink_object_from_scene(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)
- self.link_object_to_scene(context, 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()
- m = p.archipack_material.add()
- m.category = 'window'
- m.material = o.archipack_material[0].material
- 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)
- h.location = handle.location.copy()
- elif h is not None:
- self.unlink_object_from_scene(h)
- bpy.data.objects.remove(h, do_unlink=True)
-
- p.location = child.location.copy()
-
- # restore context
- context.view_layer.objects.active = o
-
- 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
- self.link_object_to_scene(context, l_hole)
- for mat in hole.data.materials:
- l_hole.data.materials.append(mat)
- 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_set(state=True)
- context.view_layer.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_portal(context, o, linked, childs)
- 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,
- enable_glass=self.enable_glass,
- material=o.archipack_material[0].material
- )
- 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_set(state=True)
- context.view_layer.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.enable_glass = self.enable_glass
- props.update(context)
- # location y + frame width. frame depends on chosen 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_portal(context, o)
- 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)
- self.link_object_to_scene(context, hole_obj)
- hole_obj['archipack_hole'] = True
- hole_obj.parent = o
- hole_obj.matrix_world = o.matrix_world.copy()
-
- """
- hole_obj.data.materials.clear()
-
- for mat in o.data.materials:
- hole_obj.data.materials.append(mat)
- # 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
- self.link_object_to_scene(context, 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_set(state=True)
- context.view_layer.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='VIEW_PAN')
- 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='ADD')
- row.operator("archipack.window_preset", text="", icon='REMOVE').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", text="Components", emboss=False)
- else:
- row.prop(prop, "display_detail", icon="TRIA_RIGHT", text="Components", emboss=False)
-
- if prop.display_detail:
- box = layout.box()
- box.prop(prop, 'enable_glass')
- box = layout.box()
- box.label(text="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", text="Rows", emboss=False)
- else:
- row.prop(prop, "display_panels", icon="TRIA_RIGHT", 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", text="Materials", emboss=False)
- else:
- row.prop(prop, "display_materials", icon="TRIA_RIGHT", text="Materials", emboss=False)
- if prop.display_materials:
- box = layout.box()
- box.label(text="Hole")
- box.prop(prop, 'hole_inside_mat')
- box.prop(prop, 'hole_outside_mat')
-
- layout.prop(prop, 'portal', icon="LIGHT_AREA")
-
-
-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(text="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
- self.link_object_to_scene(context, o)
- o.select_set(state=True)
- context.view_layer.objects.active = o
- self.add_material(o)
- self.load_preset(d)
- # select frame
- o.select_set(state=True)
- context.view_layer.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 child.type == 'LIGHT':
- d = child.data
- self.unlink_object_from_scene(child)
- bpy.data.objects.remove(child)
- bpy.data.lights.remove(d)
- elif 'archipack_hole' in child:
- self.unlink_object_from_scene(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:
- self.unlink_object_from_scene(handle)
- bpy.data.objects.remove(handle, do_unlink=True)
- self.unlink_object_from_scene(child)
- bpy.data.objects.remove(child, do_unlink=True)
- self.unlink_object_from_scene(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_set(state=True)
- context.view_layer.objects.active = o
-
- def unique(self, context):
- act = context.active_object
- sel = context.selected_objects[:]
- bpy.ops.object.select_all(action="DESELECT")
- for o in sel:
- if archipack_window.filter(o):
- o.select_set(state=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_set(state=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.view_layer.objects.active = act
- for o in sel:
- o.select_set(state=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_set(state=True)
- context.view_layer.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(ArchipackDrawTool, 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 = []
- object_name = ""
-
- @classmethod
- def poll(cls, context):
- return True
-
- def draw(self, context):
- layout = self.layout
- row = layout.row()
- row.label(text="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_set(state=True)
- context.view_layer.objects.active = o
-
- if event.shift:
- bpy.ops.archipack.window(mode="UNIQUE")
-
- # instance subs
- new_w = o.copy()
- new_w.data = o.data
- self.link_object_to_scene(context, new_w)
- for child in o.children:
- if "archipack_hole" not in child:
- new_c = child.copy()
- new_c.data = child.data
- new_c.parent = new_w
- self.link_object_to_scene(context, new_c)
- # dup handle if any
- for c in child.children:
- new_h = c.copy()
- new_h.data = c.data
- new_h.parent = new_c
- self.link_object_to_scene(context, new_h)
-
- o = new_w
- o.select_set(state=True)
- context.view_layer.objects.active = o
-
- else:
- bpy.ops.archipack.window(auto_manipulate=False, filepath=self.filepath)
- o = context.active_object
-
- self.object_name = o.name
-
- bpy.ops.archipack.generate_hole('INVOKE_DEFAULT')
- o.select_set(state=True)
- context.view_layer.objects.active = o
-
- def modal(self, context, event):
-
- context.area.tag_redraw()
- o = context.scene.objects.get(self.object_name.strip())
-
- if o is None:
- return {'FINISHED'}
-
- 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_viewport = True
- hole.hide_viewport = True
-
- res, tM, wall, width, y, z_offset = self.mouse_hover_wall(context, event)
-
- if hole is not None:
- o.hide_viewport = False
- hole.hide_viewport = 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 {'C'}:
- bpy.ops.archipack.window(mode='DELETE')
- self.feedback.disable()
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- bpy.ops.archipack.window_preset_menu('INVOKE_DEFAULT', preset_operator="archipack.window_draw")
- return {'FINISHED'}
-
- if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}:
- if wall is not None:
- o.select_set(state=True)
- context.view_layer.objects.active = wall
- wall.select_set(state=True)
- if bpy.ops.archipack.single_boolean.poll():
- bpy.ops.archipack.single_boolean()
-
- wall.select_set(state=False)
- # o must be a window here
- if d is not None:
- context.view_layer.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.view_layer.objects.active = last
- bpy.ops.archipack.window(mode="DELETE")
- context.view_layer.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.view_layer.objects.active = None
- bpy.ops.object.select_all(action="DESELECT")
- if o is not None:
- o.select_set(state=True)
- context.view_layer.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'),
- ('C', 'Choose another window'),
- ('SHIFT', 'Make independent 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'}
-
-
-class ARCHIPACK_OT_window_portals(Operator):
- bl_idname = "archipack.window_portals"
- bl_label = "Portals"
- bl_description = "Create portal for each window"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(self, context):
- return True
-
- def invoke(self, context, event):
- for o in context.scene.objects:
- d = archipack_window.datablock(o)
- if d is not None:
- d.update_portal(context)
-
- return {'FINISHED'}
-
-
-# ------------------------------------------------------------------
-# Define operator class to create object
-# ------------------------------------------------------------------
-
-
-class ARCHIPACK_OT_window_panel(ArchipackCollectionManager, 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
- )
- material : StringProperty(
- name="material",
- default=""
- )
- enable_glass : BoolProperty(
- name="Enable glass",
- default=True
- )
-
- def draw(self, context):
- layout = self.layout
- row = layout.row()
- row.label(text="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
- d.enable_glass = self.enable_glass
- self.link_object_to_scene(context, o)
- o.select_set(state=True)
- context.view_layer.objects.active = o
- m = o.archipack_material.add()
- m.category = "window"
- m.material = self.material
- 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)
- return o
-
- def execute(self, context):
- if context.mode == "OBJECT":
- o = self.create(context)
- o.select_set(state=True)
- context.view_layer.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_description = "Show Window Presets"
- 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)
- bpy.utils.register_class(ARCHIPACK_OT_window_portals)
-
-
-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)
- bpy.utils.unregister_class(ARCHIPACK_OT_window_portals)
diff --git a/archipack/bmesh_utils.py b/archipack/bmesh_utils.py
deleted file mode 100644
index 16be7a54..00000000
--- a/archipack/bmesh_utils.py
+++ /dev/null
@@ -1,315 +0,0 @@
-# -*- 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_set(state=True)
- context.view_layer.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 bmesh_join(context, o, list_of_bmeshes, normal_update=False):
- """
- takes as input a list of bm references and outputs a single merged bmesh
- allows an additional 'normal_update=True' to force _normal_ calculations.
- """
- bm = BmeshEdit._start(context, o)
-
- add_vert = bm.verts.new
- add_face = bm.faces.new
- add_edge = bm.edges.new
-
- for bm_to_add in list_of_bmeshes:
- offset = len(bm.verts)
-
- for v in bm_to_add.verts:
- add_vert(v.co)
-
- bm.verts.index_update()
- bm.verts.ensure_lookup_table()
-
- if bm_to_add.faces:
- layer = bm_to_add.loops.layers.uv.verify()
- dest = bm.loops.layers.uv.verify()
- for face in bm_to_add.faces:
- f = add_face(tuple(bm.verts[i.index + offset] for i in face.verts))
- f.material_index = face.material_index
- for j, loop in enumerate(face.loops):
- f.loops[j][dest].uv = loop[layer].uv
- bm.faces.index_update()
-
- if bm_to_add.edges:
- for edge in bm_to_add.edges:
- edge_seq = tuple(bm.verts[i.index + offset] for i in edge.verts)
- try:
- add_edge(edge_seq)
- except ValueError:
- # edge exists!
- pass
- bm.edges.index_update()
-
- # cleanup
- for old_bm in list_of_bmeshes:
- old_bm.free()
-
- if normal_update:
- bm.normal_update()
-
- BmeshEdit._end(bm, o)
-
- @staticmethod
- def _end(bm, o):
- """
- private, end bmesh editing of active object
- """
- bm.normal_update()
- bmesh.update_edit_mesh(o.data, loop_triangles=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, temporary=False):
-
- if temporary:
- bm = bmesh.new()
- else:
- bm = BmeshEdit._start(context, o)
- bm.clear()
-
- for v in verts:
- bm.verts.new(v)
- bm.verts.index_update()
- bm.verts.ensure_lookup_table()
-
- for f in faces:
- bm.faces.new([bm.verts[i] for i in f])
- bm.faces.index_update()
- bm.faces.ensure_lookup_table()
-
- if matids is not None:
- BmeshEdit._matids(bm, matids)
-
- if uvs is not None:
- BmeshEdit._uvs(bm, uvs)
-
- if temporary:
- return bm
-
- 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='OFFSET',
- 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
deleted file mode 100644
index 92503c82..00000000
--- a/archipack/icons/archipack.png
+++ /dev/null
Binary files differ
diff --git a/archipack/icons/detect.png b/archipack/icons/detect.png
deleted file mode 100644
index 9c10f604..00000000
--- a/archipack/icons/detect.png
+++ /dev/null
Binary files differ
diff --git a/archipack/icons/door.png b/archipack/icons/door.png
deleted file mode 100644
index dc975d4d..00000000
--- a/archipack/icons/door.png
+++ /dev/null
Binary files differ
diff --git a/archipack/icons/fence.png b/archipack/icons/fence.png
deleted file mode 100644
index f32dcc7e..00000000
--- a/archipack/icons/fence.png
+++ /dev/null
Binary files differ
diff --git a/archipack/icons/floor.png b/archipack/icons/floor.png
deleted file mode 100644
index 1590c335..00000000
--- a/archipack/icons/floor.png
+++ /dev/null
Binary files differ
diff --git a/archipack/icons/polygons.png b/archipack/icons/polygons.png
deleted file mode 100644
index b434068c..00000000
--- a/archipack/icons/polygons.png
+++ /dev/null
Binary files differ
diff --git a/archipack/icons/roof.png b/archipack/icons/roof.png
deleted file mode 100644
index 7af48808..00000000
--- a/archipack/icons/roof.png
+++ /dev/null
Binary files differ
diff --git a/archipack/icons/selection.png b/archipack/icons/selection.png
deleted file mode 100644
index e4a7e82b..00000000
--- a/archipack/icons/selection.png
+++ /dev/null
Binary files differ
diff --git a/archipack/icons/slab.png b/archipack/icons/slab.png
deleted file mode 100644
index 292ea52e..00000000
--- a/archipack/icons/slab.png
+++ /dev/null
Binary files differ
diff --git a/archipack/icons/stair.png b/archipack/icons/stair.png
deleted file mode 100644
index 5ce4d705..00000000
--- a/archipack/icons/stair.png
+++ /dev/null
Binary files differ
diff --git a/archipack/icons/truss.png b/archipack/icons/truss.png
deleted file mode 100644
index 72ca9157..00000000
--- a/archipack/icons/truss.png
+++ /dev/null
Binary files differ
diff --git a/archipack/icons/union.png b/archipack/icons/union.png
deleted file mode 100644
index 11b11472..00000000
--- a/archipack/icons/union.png
+++ /dev/null
Binary files differ
diff --git a/archipack/icons/wall.png b/archipack/icons/wall.png
deleted file mode 100644
index 1335a590..00000000
--- a/archipack/icons/wall.png
+++ /dev/null
Binary files differ
diff --git a/archipack/icons/window.png b/archipack/icons/window.png
deleted file mode 100644
index 74be2e0e..00000000
--- a/archipack/icons/window.png
+++ /dev/null
Binary files differ
diff --git a/archipack/panel.py b/archipack/panel.py
deleted file mode 100644
index 7339cc3a..00000000
--- a/archipack/panel.py
+++ /dev/null
@@ -1,715 +0,0 @@
-# -*- 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 which 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 f in range(n_profil_faces):
- idmat.append(self.idmat[f])
- 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.py b/archipack/presets/archipack_door/160x200_dual.py
deleted file mode 100644
index 7a9e5ebc..00000000
--- a/archipack/presets/archipack_door/160x200_dual.py
+++ /dev/null
@@ -1,23 +0,0 @@
-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.py b/archipack/presets/archipack_door/400x240_garage.py
deleted file mode 100644
index 2060cc3b..00000000
--- a/archipack/presets/archipack_door/400x240_garage.py
+++ /dev/null
@@ -1,23 +0,0 @@
-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.py b/archipack/presets/archipack_door/80x200.py
deleted file mode 100644
index a29e3ddc..00000000
--- a/archipack/presets/archipack_door/80x200.py
+++ /dev/null
@@ -1,23 +0,0 @@
-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.py b/archipack/presets/archipack_fence/glass_panels.py
deleted file mode 100644
index 2d150b71..00000000
--- a/archipack/presets/archipack_fence/glass_panels.py
+++ /dev/null
@@ -1,67 +0,0 @@
-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.py b/archipack/presets/archipack_fence/inox_glass_concrete.py
deleted file mode 100644
index 80d3fb6c..00000000
--- a/archipack/presets/archipack_fence/inox_glass_concrete.py
+++ /dev/null
@@ -1,64 +0,0 @@
-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.py b/archipack/presets/archipack_fence/metal.py
deleted file mode 100644
index 5e7ecbfd..00000000
--- a/archipack/presets/archipack_fence/metal.py
+++ /dev/null
@@ -1,67 +0,0 @@
-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.py b/archipack/presets/archipack_fence/metal_glass.py
deleted file mode 100644
index fb5149cb..00000000
--- a/archipack/presets/archipack_fence/metal_glass.py
+++ /dev/null
@@ -1,67 +0,0 @@
-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.py b/archipack/presets/archipack_fence/wood.py
deleted file mode 100644
index 9a9a42d9..00000000
--- a/archipack/presets/archipack_fence/wood.py
+++ /dev/null
@@ -1,67 +0,0 @@
-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/boards_200x20.py b/archipack/presets/archipack_floor/boards_200x20.py
deleted file mode 100644
index d256cf42..00000000
--- a/archipack/presets/archipack_floor/boards_200x20.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-bpy.ops.archipack.material(category='floor', material='DEFAULT')
-d.add_grout = False
-d.bevel = False
-d.bevel_amount = 0.001
-d.board_length = 2.0
-d.board_width = 0.2
-d.boards_in_group = 5
-d.length_spacing = 0.002
-d.length_variance = 50
-d.matid = 7
-d.max_boards = 20
-d.mortar_depth = 0.001
-d.offset = 50.0
-d.offset_variance = 50
-d.pattern = 'boards'
-d.random_offset = True
-d.random_uvs = True
-d.short_board_length = 0.15
-d.spacing = 0.0
-d.thickness = 0.02
-d.thickness_variance = 25.0
-d.tile_length = 0.3
-d.tile_width = 0.2
-d.vary_length = False
-d.vary_materials = True
-d.vary_thickness = False
-d.vary_width = False
-d.width_spacing = 0.002
-d.width_variance = 50.0
diff --git a/archipack/presets/archipack_floor/herringbone_50x10.py b/archipack/presets/archipack_floor/herringbone_50x10.py
deleted file mode 100644
index 5e12e365..00000000
--- a/archipack/presets/archipack_floor/herringbone_50x10.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-bpy.ops.archipack.material(category='floor', material='DEFAULT')
-d.add_grout = False
-d.bevel = False
-d.bevel_amount = 0.001
-d.board_length = 2.0
-d.board_width = 0.1
-d.boards_in_group = 4
-d.length_spacing = 0.002
-d.length_variance = 50.0
-d.matid = 7
-d.max_boards = 20
-d.mortar_depth = 0.001
-d.offset = 50.0
-d.offset_variance = 50.0
-d.pattern = 'herringbone'
-d.random_offset = False
-d.random_uvs = True
-d.short_board_length = 0.5
-d.spacing = 0.0
-d.thickness = 0.02
-d.thickness_variance = 25.0
-d.tile_length = 0.3
-d.tile_width = 0.2
-d.vary_length = False
-d.vary_materials = True
-d.vary_thickness = False
-d.vary_width = False
-d.width_spacing = 0.002
-d.width_variance = 50.0
diff --git a/archipack/presets/archipack_floor/herringbone_p_50x10.py b/archipack/presets/archipack_floor/herringbone_p_50x10.py
deleted file mode 100644
index 15946169..00000000
--- a/archipack/presets/archipack_floor/herringbone_p_50x10.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-bpy.ops.archipack.material(category='floor', material='DEFAULT')
-d.add_grout = False
-d.bevel = False
-d.bevel_amount = 0.001
-d.board_length = 2.0
-d.board_width = 0.1
-d.boards_in_group = 4
-d.length_spacing = 0.002
-d.length_variance = 50.0
-d.matid = 7
-d.max_boards = 20
-d.mortar_depth = 0.001
-d.offset = 50.0
-d.offset_variance = 50.0
-d.pattern = 'herringbone_parquet'
-d.random_offset = False
-d.random_uvs = True
-d.short_board_length = 0.5
-d.spacing = 0.0
-d.thickness = 0.02
-d.thickness_variance = 25.0
-d.tile_length = 0.3
-d.tile_width = 0.2
-d.vary_length = False
-d.vary_materials = True
-d.vary_thickness = False
-d.vary_width = False
-d.width_spacing = 0.002
-d.width_variance = 50.0
diff --git a/archipack/presets/archipack_floor/hexagon_10.py b/archipack/presets/archipack_floor/hexagon_10.py
deleted file mode 100644
index 5e0b7ce5..00000000
--- a/archipack/presets/archipack_floor/hexagon_10.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-bpy.ops.archipack.material(category='floor', material='TILES')
-d.add_grout = True
-d.bevel = True
-d.bevel_amount = 0.0015
-d.board_length = 2.0
-d.board_width = 0.2
-d.boards_in_group = 5
-d.length_spacing = 0.002
-d.length_variance = 50
-d.matid = 7
-d.max_boards = 20
-d.mortar_depth = 0.0015
-d.offset = 0.0
-d.offset_variance = 50
-d.pattern = 'hexagon'
-d.random_offset = False
-d.random_uvs = True
-d.short_board_length = 0.15
-d.spacing = 0.005
-d.thickness = 0.1
-d.thickness_variance = 25.0
-d.tile_length = 0.3
-d.tile_width = 0.1
-d.vary_length = False
-d.vary_materials = True
-d.vary_thickness = False
-d.vary_width = False
-d.width_spacing = 0.002
-d.width_variance = 50.0
diff --git a/archipack/presets/archipack_floor/hopscotch_30x30.py b/archipack/presets/archipack_floor/hopscotch_30x30.py
deleted file mode 100644
index b662a0e3..00000000
--- a/archipack/presets/archipack_floor/hopscotch_30x30.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-bpy.ops.archipack.material(category='floor', material='TILES')
-d.add_grout = True
-d.bevel = True
-d.bevel_amount = 0.0015
-d.board_length = 2.0
-d.board_width = 0.2
-d.boards_in_group = 5
-d.length_spacing = 0.002
-d.length_variance = 50
-d.matid = 7
-d.max_boards = 20
-d.mortar_depth = 0.0015
-d.offset = 0.0
-d.offset_variance = 50
-d.pattern = 'hopscotch'
-d.random_offset = False
-d.random_uvs = True
-d.short_board_length = 0.15
-d.spacing = 0.005
-d.thickness = 0.1
-d.thickness_variance = 25.0
-d.tile_length = 0.3
-d.tile_width = 0.3
-d.vary_length = False
-d.vary_materials = True
-d.vary_thickness = False
-d.vary_width = False
-d.width_spacing = 0.002
-d.width_variance = 50.0
diff --git a/archipack/presets/archipack_floor/parquet_15x3.py b/archipack/presets/archipack_floor/parquet_15x3.py
deleted file mode 100644
index e7ee7c00..00000000
--- a/archipack/presets/archipack_floor/parquet_15x3.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-bpy.ops.archipack.material(category='floor', material='DEFAULT')
-d.add_grout = False
-d.bevel = False
-d.bevel_amount = 0.001
-d.board_length = 2.0
-d.board_width = 0.1
-d.boards_in_group = 5
-d.length_spacing = 0.002
-d.length_variance = 50.0
-d.matid = 7
-d.max_boards = 20
-d.mortar_depth = 0.001
-d.offset = 50.0
-d.offset_variance = 50.0
-d.pattern = 'square_parquet'
-d.random_offset = False
-d.random_uvs = True
-d.short_board_length = 0.15
-d.spacing = 0.0
-d.thickness = 0.02
-d.thickness_variance = 25.0
-d.tile_length = 0.3
-d.tile_width = 0.2
-d.vary_length = False
-d.vary_materials = True
-d.vary_thickness = False
-d.vary_width = False
-d.width_spacing = 0.002
-d.width_variance = 50.0
diff --git a/archipack/presets/archipack_floor/stepping_stone_30x30.py b/archipack/presets/archipack_floor/stepping_stone_30x30.py
deleted file mode 100644
index bace036f..00000000
--- a/archipack/presets/archipack_floor/stepping_stone_30x30.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-bpy.ops.archipack.material(category='floor', material='TILES')
-d.add_grout = True
-d.bevel = True
-d.bevel_amount = 0.0015
-d.board_length = 2.0
-d.board_width = 0.2
-d.boards_in_group = 5
-d.length_spacing = 0.002
-d.length_variance = 50
-d.matid = 7
-d.max_boards = 20
-d.mortar_depth = 0.0015
-d.offset = 0.0
-d.offset_variance = 50
-d.pattern = 'stepping_stone'
-d.random_offset = False
-d.random_uvs = True
-d.short_board_length = 0.15
-d.spacing = 0.005
-d.thickness = 0.1
-d.thickness_variance = 25.0
-d.tile_length = 0.3
-d.tile_width = 0.3
-d.vary_length = False
-d.vary_materials = True
-d.vary_thickness = False
-d.vary_width = False
-d.width_spacing = 0.002
-d.width_variance = 50.0
diff --git a/archipack/presets/archipack_floor/tile_30x60.py b/archipack/presets/archipack_floor/tile_30x60.py
deleted file mode 100644
index 091b3d0e..00000000
--- a/archipack/presets/archipack_floor/tile_30x60.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-bpy.ops.archipack.material(category='floor', material='TILES')
-d.add_grout = True
-d.bevel = True
-d.bevel_amount = 0.0015
-d.board_length = 2.0
-d.board_width = 0.2
-d.boards_in_group = 5
-d.length_spacing = 0.002
-d.length_variance = 50
-d.matid = 7
-d.max_boards = 20
-d.mortar_depth = 0.0015
-d.offset = 0.0
-d.offset_variance = 50
-d.pattern = 'regular_tile'
-d.random_offset = False
-d.random_uvs = True
-d.short_board_length = 0.15
-d.spacing = 0.005
-d.thickness = 0.1
-d.thickness_variance = 25.0
-d.tile_length = 0.3
-d.tile_width = 0.6
-d.vary_length = False
-d.vary_materials = True
-d.vary_thickness = False
-d.vary_width = False
-d.width_spacing = 0.002
-d.width_variance = 50.0
diff --git a/archipack/presets/archipack_floor/windmill_30x30.py b/archipack/presets/archipack_floor/windmill_30x30.py
deleted file mode 100644
index 753a2de6..00000000
--- a/archipack/presets/archipack_floor/windmill_30x30.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-bpy.ops.archipack.material(category='floor', material='TILES')
-d.add_grout = True
-d.bevel = True
-d.bevel_amount = 0.0015
-d.board_length = 2.0
-d.board_width = 0.2
-d.boards_in_group = 5
-d.length_spacing = 0.002
-d.length_variance = 50
-d.matid = 7
-d.max_boards = 20
-d.mortar_depth = 0.0015
-d.offset = 50.0
-d.offset_variance = 50
-d.pattern = 'windmill'
-d.random_offset = True
-d.random_uvs = True
-d.short_board_length = 0.15
-d.spacing = 0.005
-d.thickness = 0.1
-d.thickness_variance = 25.0
-d.tile_length = 0.3
-d.tile_width = 0.3
-d.vary_length = False
-d.vary_materials = True
-d.vary_thickness = False
-d.vary_width = False
-d.width_spacing = 0.002
-d.width_variance = 50.0
diff --git a/archipack/presets/archipack_materials/door.txt b/archipack/presets/archipack_materials/door.txt
deleted file mode 100644
index 18951498..00000000
--- a/archipack/presets/archipack_materials/door.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-DEFAULT##|##Door_inside
-DEFAULT##|##Door_outside
-DEFAULT##|##Door_glass
-DEFAULT##|##Door_metal
diff --git a/archipack/presets/archipack_materials/fence.txt b/archipack/presets/archipack_materials/fence.txt
deleted file mode 100644
index 00827582..00000000
--- a/archipack/presets/archipack_materials/fence.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-DEFAULT##|##Fence_wood
-DEFAULT##|##Fence_metal
-DEFAULT##|##Fence_glass
-DEFAULT##|##Fence_concrete
diff --git a/archipack/presets/archipack_materials/floor.txt b/archipack/presets/archipack_materials/floor.txt
deleted file mode 100644
index 1afa9329..00000000
--- a/archipack/presets/archipack_materials/floor.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-DEFAULT##|##Floor_grout
-DEFAULT##|##Floor_alt1
-DEFAULT##|##Floor_alt2
-DEFAULT##|##Floor_alt3
-DEFAULT##|##Floor_alt4
-DEFAULT##|##Floor_alt5
-DEFAULT##|##Floor_alt6
-DEFAULT##|##Floor_alt7
-DEFAULT##|##Floor_alt8
-DEFAULT##|##Floor_alt9
-DEFAULT##|##Floor_alt10
-TILES##|##Floor_grout
-TILES##|##Floor_tiles_alt1
-TILES##|##Floor_tiles_alt2
-TILES##|##Floor_tiles_alt3
-TILES##|##Floor_tiles_alt4
-TILES##|##Floor_tiles_alt5
-TILES##|##Floor_tiles_alt6
-TILES##|##Floor_alt7
-TILES##|##Floor_alt8
-TILES##|##Floor_alt9
-TILES##|##Floor_alt10
diff --git a/archipack/presets/archipack_materials/handle.txt b/archipack/presets/archipack_materials/handle.txt
deleted file mode 100644
index 458cb1c2..00000000
--- a/archipack/presets/archipack_materials/handle.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-DEFAULT##|##Handle_inside
-DEFAULT##|##Handle_outside
diff --git a/archipack/presets/archipack_materials/roof.txt b/archipack/presets/archipack_materials/roof.txt
deleted file mode 100644
index 4738c544..00000000
--- a/archipack/presets/archipack_materials/roof.txt
+++ /dev/null
@@ -1,48 +0,0 @@
-DEFAULT##|##Roof_sheeting
-DEFAULT##|##Roof_rakes
-DEFAULT##|##Roof_eaves
-DEFAULT##|##Roof_ridge
-DEFAULT##|##Roof_rafter
-DEFAULT##|##Roof_valley
-DEFAULT##|##Roof_hip_tiles
-DEFAULT##|##Roof_tiles
-DEFAULT##|##Roof_tiles2
-DEFAULT##|##Roof_tiles3
-DEFAULT##|##Roof_tiles4
-DEFAULT##|##Roof_tiles5
-STONE##|##Roof_sheeting
-STONE##|##Roof_rakes
-STONE##|##Roof_eaves
-STONE##|##Roof_ridge
-STONE##|##Roof_rafter
-STONE##|##Roof_valley
-STONE##|##Roof_hip_stone
-STONE##|##Roof_tiles_stone
-STONE##|##Roof_tiles_stone2
-STONE##|##Roof_tiles_stone3
-STONE##|##Roof_tiles_stone4
-STONE##|##Roof_tiles_stone5
-BLACK##|##Roof_sheeting
-BLACK##|##Roof_rakes
-BLACK##|##Roof_eaves
-BLACK##|##Roof_ridge
-BLACK##|##Roof_rafter
-BLACK##|##Roof_valley
-BLACK##|##Roof_hip_black
-BLACK##|##Roof_tiles_black
-BLACK##|##Roof_tiles_black2
-BLACK##|##Roof_tiles_black3
-BLACK##|##Roof_tiles_black4
-BLACK##|##Roof_tiles_black5
-METAL##|##Roof_sheeting
-METAL##|##Roof_rakes
-METAL##|##Roof_eaves
-METAL##|##Roof_ridge
-METAL##|##Roof_rafter
-METAL##|##Roof_valley
-METAL##|##Roof_hip_metal
-METAL##|##Roof_metal
-METAL##|##Roof_metal2
-METAL##|##Roof_metal3
-METAL##|##Roof_metal4
-METAL##|##Roof_metal5
diff --git a/archipack/presets/archipack_materials/slab.txt b/archipack/presets/archipack_materials/slab.txt
deleted file mode 100644
index 8d3490fe..00000000
--- a/archipack/presets/archipack_materials/slab.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-DEFAULT##|##Slab_bottom
-DEFAULT##|##Slab_top
-DEFAULT##|##Slab_side
diff --git a/archipack/presets/archipack_materials/stair.txt b/archipack/presets/archipack_materials/stair.txt
deleted file mode 100644
index 44966d35..00000000
--- a/archipack/presets/archipack_materials/stair.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-DEFAULT##|##Stair_ceiling
-DEFAULT##|##Stair_white
-DEFAULT##|##Stair_concrete
-DEFAULT##|##Stair_wood
-DEFAULT##|##Stair_metal
-DEFAULT##|##Stair_glass
diff --git a/archipack/presets/archipack_materials/truss.txt b/archipack/presets/archipack_materials/truss.txt
deleted file mode 100644
index 00718d4b..00000000
--- a/archipack/presets/archipack_materials/truss.txt
+++ /dev/null
@@ -1 +0,0 @@
-DEFAULT##|##Truss_truss
diff --git a/archipack/presets/archipack_materials/wall2.txt b/archipack/presets/archipack_materials/wall2.txt
deleted file mode 100644
index 789c285d..00000000
--- a/archipack/presets/archipack_materials/wall2.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-DEFAULT##|##Wall2_inside
-DEFAULT##|##Wall2_outside
-DEFAULT##|##Wall2_cuts
-DEFAULT##|##Wall2_alt1
-DEFAULT##|##Wall2_alt2
-DEFAULT##|##Wall2_alt3
-DEFAULT##|##Wall2_alt4
-DEFAULT##|##Wall2_alt5
diff --git a/archipack/presets/archipack_materials/window.txt b/archipack/presets/archipack_materials/window.txt
deleted file mode 100644
index 8f5f8575..00000000
--- a/archipack/presets/archipack_materials/window.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-DEFAULT##|##Window_inside
-DEFAULT##|##Window_outside
-DEFAULT##|##Window_glass
-DEFAULT##|##Window_metal
-DEFAULT##|##Window_stone
-DEFAULT##|##Window_blind
diff --git a/archipack/presets/archipack_roof/braas_1.py b/archipack/presets/archipack_roof/braas_1.py
deleted file mode 100644
index 203d44db..00000000
--- a/archipack/presets/archipack_roof/braas_1.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_roof[0]
-bpy.ops.archipack.material(category='roof', material='DEFAULT')
-d.tile_model = 'BRAAS1'
-d.tile_size_z = 0.05
-d.tile_border = 0.0
-d.tile_space_x = 0.205
-d.tile_couloir = 0.05
-d.hip_size_x = 0.42
-d.tile_altitude = 0.1
-d.tile_fit_y = True
-d.tile_side = 0.0
-d.hip_space_x = 0.4
-d.tile_size_x = 0.2
-d.tile_size_y = 0.32
-d.tile_offset = 0.0
-d.tile_bevel_amt = 25.0
-d.hip_size_z = 0.18
-d.tile_solidify = True
-d.tile_height = 0.02
-d.tile_bevel = True
-d.hip_model = 'ROUND'
-d.tile_space_y = 0.3
-d.hip_enable = True
-d.hip_size_y = 0.18
-d.tile_enable = True
-d.tile_alternate = False
-d.hip_alt = 0.13
-d.tile_bevel_segs = 2
-d.tile_fit_x = False
-d.valley_enable = True
diff --git a/archipack/presets/archipack_roof/braas_2.py b/archipack/presets/archipack_roof/braas_2.py
deleted file mode 100644
index cdd77493..00000000
--- a/archipack/presets/archipack_roof/braas_2.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_roof[0]
-bpy.ops.archipack.material(category='roof', material='DEFAULT')
-d.tile_model = 'BRAAS2'
-d.tile_size_z = 0.05
-d.tile_border = 0.0
-d.tile_space_x = 0.205
-d.tile_couloir = 0.05
-d.hip_size_x = 0.42
-d.tile_altitude = 0.1
-d.tile_fit_y = True
-d.tile_side = 0.0
-d.hip_space_x = 0.4
-d.tile_size_x = 0.2
-d.tile_size_y = 0.32
-d.tile_offset = 0.0
-d.tile_bevel_amt = 25.0
-d.hip_size_z = 0.18
-d.tile_solidify = True
-d.tile_height = 0.02
-d.tile_bevel = True
-d.hip_model = 'ROUND'
-d.tile_space_y = 0.3
-d.hip_enable = True
-d.hip_size_y = 0.18
-d.tile_enable = True
-d.tile_alternate = False
-d.hip_alt = 0.13
-d.tile_bevel_segs = 2
-d.tile_fit_x = False
-d.valley_enable = True
diff --git a/archipack/presets/archipack_roof/eternit.py b/archipack/presets/archipack_roof/eternit.py
deleted file mode 100644
index 4d9f064b..00000000
--- a/archipack/presets/archipack_roof/eternit.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_roof[0]
-bpy.ops.archipack.material(category='roof', material='BLACK')
-d.tile_model = 'ETERNIT'
-d.tile_size_z = 0.01
-d.tile_border = 0.0
-d.tile_space_x = 0.41
-d.tile_couloir = 0.05
-d.hip_size_x = 0.4
-d.tile_altitude = 0.1
-d.tile_fit_y = False
-d.tile_side = 0.0
-d.hip_space_x = 0.4
-d.tile_size_x = 0.4
-d.tile_size_y = 0.2
-d.tile_offset = 0.0
-d.tile_bevel_amt = 50.0
-d.hip_size_z = 0.01
-d.tile_solidify = True
-d.tile_height = 0.004
-d.tile_bevel = False
-d.hip_model = 'ETERNIT'
-d.tile_space_y = 0.2
-d.hip_enable = True
-d.hip_size_y = 0.3
-d.tile_enable = True
-d.tile_alternate = True
-d.hip_alt = 0.12
-d.tile_bevel_segs = 3
-d.tile_fit_x = False
-d.valley_enable = True
diff --git a/archipack/presets/archipack_roof/lauze.py b/archipack/presets/archipack_roof/lauze.py
deleted file mode 100644
index 2eeca130..00000000
--- a/archipack/presets/archipack_roof/lauze.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_roof[0]
-bpy.ops.archipack.material(category='roof', material='STONE')
-d.tile_model = 'LAUZE'
-d.tile_size_z = 0.04
-d.tile_border = 0.0
-d.tile_space_x = 0.61
-d.tile_couloir = 0.05
-d.hip_size_x = 0.42
-d.tile_altitude = 0.1
-d.tile_fit_y = False
-d.tile_side = 0.0
-d.hip_space_x = 0.4
-d.tile_size_x = 0.6
-d.tile_size_y = 0.6
-d.tile_offset = 0.0
-d.tile_bevel_amt = 50.0
-d.hip_size_z = 0.06
-d.tile_solidify = True
-d.tile_height = 0.02
-d.tile_bevel = False
-d.hip_model = 'FLAT'
-d.tile_space_y = 0.3
-d.hip_enable = True
-d.hip_size_y = 0.15
-d.tile_enable = True
-d.tile_alternate = True
-d.hip_alt = 0.13
-d.tile_bevel_segs = 3
-d.tile_fit_x = False
-d.valley_enable = True
diff --git a/archipack/presets/archipack_roof/metal.py b/archipack/presets/archipack_roof/metal.py
deleted file mode 100644
index 35f8f1e6..00000000
--- a/archipack/presets/archipack_roof/metal.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_roof[0]
-bpy.ops.archipack.material(category='roof', material='METAL')
-d.tile_side = 0.0
-d.hip_alt = 0.07
-d.tile_fit_y = False
-d.tile_space_y = 2.2
-d.tile_size_z = 0.05
-d.hip_size_z = 0.18
-d.tile_space_x = 1.0
-d.hip_size_x = 0.4
-d.hip_space_x = 0.4
-d.tile_enable = True
-d.tile_size_x = 1.0
-d.tile_border = 0.0
-d.tile_bevel = False
-d.tile_bevel_amt = 25.0
-d.tile_solidify = False
-d.tile_model = 'METAL'
-d.hip_size_y = 0.18
-d.tile_height = 0.02
-d.tile_alternate = False
-d.tile_couloir = 0.0
-d.valley_enable = False
-d.tile_size_y = 2.5
-d.tile_altitude = 0.1
-d.tile_fit_x = False
-d.hip_model = 'ROUND'
-d.hip_enable = False
-d.tile_bevel_segs = 3
-d.tile_offset = 0.0
diff --git a/archipack/presets/archipack_roof/ondule.py b/archipack/presets/archipack_roof/ondule.py
deleted file mode 100644
index f5592478..00000000
--- a/archipack/presets/archipack_roof/ondule.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_roof[0]
-bpy.ops.archipack.material(category='roof', material='METAL')
-d.tile_side = 0.0
-d.hip_alt = 0.07
-d.tile_fit_y = False
-d.tile_space_y = 2.2
-d.tile_size_z = 0.05
-d.hip_size_z = 0.18
-d.tile_space_x = 1.0
-d.hip_size_x = 0.4
-d.hip_space_x = 0.4
-d.tile_enable = True
-d.tile_size_x = 1.0
-d.tile_border = 0.0
-d.tile_bevel = True
-d.tile_bevel_amt = 25.0
-d.tile_solidify = False
-d.tile_model = 'ONDULEE'
-d.tile_height = 0.02
-d.tile_alternate = False
-d.tile_couloir = 0
-d.valley_enable = False
-d.tile_size_y = 2.5
-d.tile_altitude = 0.1
-d.tile_fit_x = False
-d.hip_model = 'ROUND'
-d.hip_enable = True
-d.tile_bevel_segs = 3
-d.tile_offset = 0.0
diff --git a/archipack/presets/archipack_roof/roman.py b/archipack/presets/archipack_roof/roman.py
deleted file mode 100644
index 92e2c1ef..00000000
--- a/archipack/presets/archipack_roof/roman.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_roof[0]
-bpy.ops.archipack.material(category='roof', material='DEFAULT')
-d.tile_model = 'ROMAN'
-d.tile_size_z = 0.16
-d.tile_border = 0.0
-d.tile_space_x = 0.2
-d.tile_couloir = 0.05
-d.hip_size_x = 0.42
-d.tile_altitude = 0.07
-d.tile_fit_y = True
-d.tile_side = 0.0
-d.hip_space_x = 0.4
-d.tile_size_x = 0.2
-d.tile_size_y = 0.3
-d.tile_offset = 0.0
-d.tile_bevel_amt = 50.0
-d.hip_size_z = 0.18
-d.tile_solidify = True
-d.tile_height = 0.02
-d.tile_bevel = True
-d.hip_model = 'ROUND'
-d.tile_space_y = 0.28
-d.hip_enable = True
-d.hip_size_y = 0.18
-d.tile_enable = True
-d.tile_alternate = False
-d.hip_alt = 0.16
-d.tile_bevel_segs = 3
-d.tile_fit_x = False
diff --git a/archipack/presets/archipack_roof/round.py b/archipack/presets/archipack_roof/round.py
deleted file mode 100644
index 74b7cb09..00000000
--- a/archipack/presets/archipack_roof/round.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_roof[0]
-bpy.ops.archipack.material(category='roof', material='DEFAULT')
-d.tile_model = 'ROUND'
-d.tile_size_z = 0.02
-d.tile_border = 0.0
-d.tile_space_x = 0.105
-d.tile_couloir = 0.05
-d.hip_size_x = 0.42
-d.tile_altitude = 0.1
-d.tile_fit_y = False
-d.tile_side = 0.0
-d.hip_space_x = 0.4
-d.tile_size_x = 0.1
-d.tile_size_y = 0.15
-d.tile_offset = 0.0
-d.tile_bevel_amt = 50.0
-d.hip_size_z = 0.15
-d.tile_solidify = True
-d.tile_height = 0.02
-d.tile_bevel = False
-d.hip_model = 'ROUND'
-d.tile_space_y = 0.07
-d.hip_enable = True
-d.hip_size_y = 0.15
-d.tile_enable = True
-d.tile_alternate = True
-d.hip_alt = 0.1
-d.tile_bevel_segs = 3
-d.tile_fit_x = False
-d.valley_enable = True
diff --git a/archipack/presets/archipack_roof/square.py b/archipack/presets/archipack_roof/square.py
deleted file mode 100644
index f8d92dd1..00000000
--- a/archipack/presets/archipack_roof/square.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_roof[0]
-bpy.ops.archipack.material(category='roof', material='BLACK')
-d.tile_model = 'PLACEHOLDER'
-d.tile_size_z = 0.01
-d.tile_border = 0.0
-d.tile_space_x = 0.401
-d.tile_couloir = 0.05
-d.hip_size_x = 0.4
-d.tile_altitude = 0.1
-d.tile_fit_y = False
-d.tile_side = 0.0
-d.hip_space_x = 0.4
-d.tile_size_x = 0.4
-d.tile_size_y = 0.4
-d.tile_offset = 0.0
-d.tile_bevel_amt = 50.0
-d.hip_size_z = 0.01
-d.tile_solidify = True
-d.tile_height = 0.004
-d.tile_bevel = False
-d.hip_model = 'ETERNIT'
-d.tile_space_y = 0.2
-d.hip_enable = True
-d.hip_size_y = 0.3
-d.tile_enable = True
-d.tile_alternate = True
-d.hip_alt = 0.12
-d.tile_bevel_segs = 3
-d.tile_fit_x = False
-d.valley_enable = True
diff --git a/archipack/presets/archipack_stair/i_wood_over_concrete.py b/archipack/presets/archipack_stair/i_wood_over_concrete.py
deleted file mode 100644
index 53b605cf..00000000
--- a/archipack/presets/archipack_stair/i_wood_over_concrete.py
+++ /dev/null
@@ -1,117 +0,0 @@
-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.py b/archipack/presets/archipack_stair/l_wood_over_concrete.py
deleted file mode 100644
index d4fc1344..00000000
--- a/archipack/presets/archipack_stair/l_wood_over_concrete.py
+++ /dev/null
@@ -1,155 +0,0 @@
-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.py b/archipack/presets/archipack_stair/o_wood_over_concrete.py
deleted file mode 100644
index 586aa990..00000000
--- a/archipack/presets/archipack_stair/o_wood_over_concrete.py
+++ /dev/null
@@ -1,136 +0,0 @@
-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.py b/archipack/presets/archipack_stair/u_wood_over_concrete.py
deleted file mode 100644
index b523dcde..00000000
--- a/archipack/presets/archipack_stair/u_wood_over_concrete.py
+++ /dev/null
@@ -1,155 +0,0 @@
-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.py b/archipack/presets/archipack_window/120x110_flat_2.py
deleted file mode 100644
index 7c7dcf9b..00000000
--- a/archipack/presets/archipack_window/120x110_flat_2.py
+++ /dev/null
@@ -1,50 +0,0 @@
-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.py b/archipack/presets/archipack_window/120x110_flat_2_elliptic.py
deleted file mode 100644
index 312f7299..00000000
--- a/archipack/presets/archipack_window/120x110_flat_2_elliptic.py
+++ /dev/null
@@ -1,58 +0,0 @@
-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.py b/archipack/presets/archipack_window/120x110_flat_2_oblique.py
deleted file mode 100644
index 010b4073..00000000
--- a/archipack/presets/archipack_window/120x110_flat_2_oblique.py
+++ /dev/null
@@ -1,50 +0,0 @@
-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.py b/archipack/presets/archipack_window/120x110_flat_2_round.py
deleted file mode 100644
index 3d0fd325..00000000
--- a/archipack/presets/archipack_window/120x110_flat_2_round.py
+++ /dev/null
@@ -1,58 +0,0 @@
-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.py b/archipack/presets/archipack_window/180x110_flat_3.py
deleted file mode 100644
index 3ae2748a..00000000
--- a/archipack/presets/archipack_window/180x110_flat_3.py
+++ /dev/null
@@ -1,50 +0,0 @@
-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.py b/archipack/presets/archipack_window/180x210_flat_3.py
deleted file mode 100644
index 825b4ffd..00000000
--- a/archipack/presets/archipack_window/180x210_flat_3.py
+++ /dev/null
@@ -1,50 +0,0 @@
-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.1
-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.py b/archipack/presets/archipack_window/180x210_rail_2.py
deleted file mode 100644
index d9f2cb89..00000000
--- a/archipack/presets/archipack_window/180x210_rail_2.py
+++ /dev/null
@@ -1,50 +0,0 @@
-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.py b/archipack/presets/archipack_window/240x210_rail_3.py
deleted file mode 100644
index bba17e78..00000000
--- a/archipack/presets/archipack_window/240x210_rail_3.py
+++ /dev/null
@@ -1,50 +0,0 @@
-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.2
-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.py b/archipack/presets/archipack_window/80x80_flat_1.py
deleted file mode 100644
index caf2980b..00000000
--- a/archipack/presets/archipack_window/80x80_flat_1.py
+++ /dev/null
@@ -1,50 +0,0 @@
-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.py b/archipack/presets/archipack_window/80x80_flat_1_circle.py
deleted file mode 100644
index 18f5c8bc..00000000
--- a/archipack/presets/archipack_window/80x80_flat_1_circle.py
+++ /dev/null
@@ -1,58 +0,0 @@
-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
deleted file mode 100644
index 1d3fb40e..00000000
--- a/archipack/presets/missing.png
+++ /dev/null
Binary files differ