diff options
author | Thomas Dinges <blender@dingto.org> | 2021-11-23 11:24:55 +0300 |
---|---|---|
committer | Thomas Dinges <blender@dingto.org> | 2021-11-23 11:24:55 +0300 |
commit | d7517a6f2a69071eab53c02a645f7651ccfffd45 (patch) | |
tree | 7a496b8ada3e764b7e6c438e30d8c2be49fb95cf | |
parent | 162cba016c8c11bcebea4d8d3cf80da9faf4ce76 (diff) |
Remove Archipack to reflect new key requirements.
https://wiki.blender.org/wiki/Process/Addons
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 Binary files differdeleted file mode 100644 index 92503c82..00000000 --- a/archipack/icons/archipack.png +++ /dev/null diff --git a/archipack/icons/detect.png b/archipack/icons/detect.png Binary files differdeleted file mode 100644 index 9c10f604..00000000 --- a/archipack/icons/detect.png +++ /dev/null diff --git a/archipack/icons/door.png b/archipack/icons/door.png Binary files differdeleted file mode 100644 index dc975d4d..00000000 --- a/archipack/icons/door.png +++ /dev/null diff --git a/archipack/icons/fence.png b/archipack/icons/fence.png Binary files differdeleted file mode 100644 index f32dcc7e..00000000 --- a/archipack/icons/fence.png +++ /dev/null diff --git a/archipack/icons/floor.png b/archipack/icons/floor.png Binary files differdeleted file mode 100644 index 1590c335..00000000 --- a/archipack/icons/floor.png +++ /dev/null diff --git a/archipack/icons/polygons.png b/archipack/icons/polygons.png Binary files differdeleted file mode 100644 index b434068c..00000000 --- a/archipack/icons/polygons.png +++ /dev/null diff --git a/archipack/icons/roof.png b/archipack/icons/roof.png Binary files differdeleted file mode 100644 index 7af48808..00000000 --- a/archipack/icons/roof.png +++ /dev/null diff --git a/archipack/icons/selection.png b/archipack/icons/selection.png Binary files differdeleted file mode 100644 index e4a7e82b..00000000 --- a/archipack/icons/selection.png +++ /dev/null diff --git a/archipack/icons/slab.png b/archipack/icons/slab.png Binary files differdeleted file mode 100644 index 292ea52e..00000000 --- a/archipack/icons/slab.png +++ /dev/null diff --git a/archipack/icons/stair.png b/archipack/icons/stair.png Binary files differdeleted file mode 100644 index 5ce4d705..00000000 --- a/archipack/icons/stair.png +++ /dev/null diff --git a/archipack/icons/truss.png b/archipack/icons/truss.png Binary files differdeleted file mode 100644 index 72ca9157..00000000 --- a/archipack/icons/truss.png +++ /dev/null diff --git a/archipack/icons/union.png b/archipack/icons/union.png Binary files differdeleted file mode 100644 index 11b11472..00000000 --- a/archipack/icons/union.png +++ /dev/null diff --git a/archipack/icons/wall.png b/archipack/icons/wall.png Binary files differdeleted file mode 100644 index 1335a590..00000000 --- a/archipack/icons/wall.png +++ /dev/null diff --git a/archipack/icons/window.png b/archipack/icons/window.png Binary files differdeleted file mode 100644 index 74be2e0e..00000000 --- a/archipack/icons/window.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index 1d3fb40e..00000000 --- a/archipack/presets/missing.png +++ /dev/null |