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

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Leger <stephen@3dservices.ch>2017-08-01 04:48:42 +0300
committerStephen Leger <stephen@3dservices.ch>2017-08-01 04:51:01 +0300
commit45cad6756f10eb708d1a17dae4a70723accc1928 (patch)
tree48e189c5e9053f6c72547ebf425fbbd4966ef840
parent15ce79c680dd63e5d54cc8ec28ad2c4d87a813ac (diff)
archipack: update to 1.2.8 add roof and freeform floors
-rw-r--r--archipack/__init__.py49
-rw-r--r--archipack/archipack_2d.py44
-rw-r--r--archipack/archipack_autoboolean.py97
-rw-r--r--archipack/archipack_cutter.py910
-rw-r--r--archipack/archipack_door.py52
-rw-r--r--archipack/archipack_floor.py2720
-rw-r--r--archipack/archipack_gl.py125
-rw-r--r--archipack/archipack_handle.py6
-rw-r--r--archipack/archipack_manipulator.py6
-rw-r--r--archipack/archipack_material.py575
-rw-r--r--archipack/archipack_object.py19
-rw-r--r--archipack/archipack_preset.py8
-rw-r--r--archipack/archipack_progressbar.py87
-rw-r--r--archipack/archipack_reference_point.py162
-rw-r--r--archipack/archipack_roof.py5376
-rw-r--r--archipack/archipack_slab.py307
-rw-r--r--archipack/archipack_snap.py5
-rw-r--r--archipack/archipack_stair.py5
-rw-r--r--archipack/archipack_wall2.py222
-rw-r--r--archipack/archipack_window.py169
-rw-r--r--archipack/bmesh_utils.py72
-rw-r--r--archipack/icons/roof.pngbin0 -> 1483 bytes
-rw-r--r--archipack/materialutils.py169
-rw-r--r--archipack/presets/archipack_floor/boards_200x20.pngbin0 -> 11237 bytes
-rw-r--r--archipack/presets/archipack_floor/boards_200x20.py30
-rw-r--r--archipack/presets/archipack_floor/herringbone_50x10.pngbin11148 -> 11228 bytes
-rw-r--r--archipack/presets/archipack_floor/herringbone_50x10.py58
-rw-r--r--archipack/presets/archipack_floor/herringbone_p_50x10.pngbin10924 -> 11099 bytes
-rw-r--r--archipack/presets/archipack_floor/herringbone_p_50x10.py58
-rw-r--r--archipack/presets/archipack_floor/hexagon_10.pngbin0 -> 14395 bytes
-rw-r--r--archipack/presets/archipack_floor/hexagon_10.py30
-rw-r--r--archipack/presets/archipack_floor/hopscotch_30x30.pngbin0 -> 12816 bytes
-rw-r--r--archipack/presets/archipack_floor/hopscotch_30x30.py30
-rw-r--r--archipack/presets/archipack_floor/parquet_15x3.pngbin13445 -> 13697 bytes
-rw-r--r--archipack/presets/archipack_floor/parquet_15x3.py58
-rw-r--r--archipack/presets/archipack_floor/planks_200x20.pngbin11644 -> 0 bytes
-rw-r--r--archipack/presets/archipack_floor/planks_200x20.py34
-rw-r--r--archipack/presets/archipack_floor/stepping_stone_30x30.pngbin0 -> 13287 bytes
-rw-r--r--archipack/presets/archipack_floor/stepping_stone_30x30.py30
-rw-r--r--archipack/presets/archipack_floor/tile_30x60.pngbin0 -> 12081 bytes
-rw-r--r--archipack/presets/archipack_floor/tile_30x60.py30
-rw-r--r--archipack/presets/archipack_floor/tiles_15x15.pngbin12939 -> 0 bytes
-rw-r--r--archipack/presets/archipack_floor/tiles_15x15.py34
-rw-r--r--archipack/presets/archipack_floor/tiles_60x30.pngbin11379 -> 0 bytes
-rw-r--r--archipack/presets/archipack_floor/tiles_60x30.py34
-rw-r--r--archipack/presets/archipack_floor/tiles_hex_10x10.pngbin13663 -> 0 bytes
-rw-r--r--archipack/presets/archipack_floor/tiles_hex_10x10.py34
-rw-r--r--archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.pngbin12511 -> 0 bytes
-rw-r--r--archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.py34
-rw-r--r--archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.pngbin11631 -> 0 bytes
-rw-r--r--archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.py34
-rw-r--r--archipack/presets/archipack_floor/windmill_30x30.pngbin0 -> 13477 bytes
-rw-r--r--archipack/presets/archipack_floor/windmill_30x30.py30
-rw-r--r--archipack/presets/archipack_materials/door.txt4
-rw-r--r--archipack/presets/archipack_materials/fence.txt4
-rw-r--r--archipack/presets/archipack_materials/floor.txt11
-rw-r--r--archipack/presets/archipack_materials/handle.txt2
-rw-r--r--archipack/presets/archipack_materials/roof.txt12
-rw-r--r--archipack/presets/archipack_materials/slab.txt3
-rw-r--r--archipack/presets/archipack_materials/stair.txt6
-rw-r--r--archipack/presets/archipack_materials/truss.txt1
-rw-r--r--archipack/presets/archipack_materials/wall2.txt8
-rw-r--r--archipack/presets/archipack_materials/window.txt6
-rw-r--r--archipack/presets/archipack_roof/braas_1.pngbin0 -> 23745 bytes
-rw-r--r--archipack/presets/archipack_roof/braas_1.py30
-rw-r--r--archipack/presets/archipack_roof/braas_2.pngbin0 -> 23796 bytes
-rw-r--r--archipack/presets/archipack_roof/braas_2.py30
-rw-r--r--archipack/presets/archipack_roof/eternit.pngbin0 -> 21808 bytes
-rw-r--r--archipack/presets/archipack_roof/eternit.py30
-rw-r--r--archipack/presets/archipack_roof/lauze.pngbin0 -> 21626 bytes
-rw-r--r--archipack/presets/archipack_roof/lauze.py30
-rw-r--r--archipack/presets/archipack_roof/metal.pngbin0 -> 20668 bytes
-rw-r--r--archipack/presets/archipack_roof/metal.py30
-rw-r--r--archipack/presets/archipack_roof/ondule.pngbin0 -> 22270 bytes
-rw-r--r--archipack/presets/archipack_roof/ondule.py29
-rw-r--r--archipack/presets/archipack_roof/roman.pngbin0 -> 25294 bytes
-rw-r--r--archipack/presets/archipack_roof/roman.py29
-rw-r--r--archipack/presets/archipack_roof/round.pngbin0 -> 22493 bytes
-rw-r--r--archipack/presets/archipack_roof/round.py30
-rw-r--r--archipack/presets/archipack_roof/square.pngbin0 -> 21214 bytes
-rw-r--r--archipack/presets/archipack_roof/square.py30
-rw-r--r--archipack/presets/archipack_stair/u_wood_over_concrete.py2
82 files changed, 10391 insertions, 1679 deletions
diff --git a/archipack/__init__.py b/archipack/__init__.py
index 5bdf9b10..93a50e66 100644
--- a/archipack/__init__.py
+++ b/archipack/__init__.py
@@ -31,7 +31,7 @@ bl_info = {
'author': 's-leger',
'license': 'GPL',
'deps': '',
- 'version': (1, 2, 6),
+ 'version': (1, 2, 8),
'blender': (2, 7, 8),
'location': 'View3D > Tools > Create > Archipack',
'warning': '',
@@ -46,6 +46,8 @@ import os
if "bpy" in locals():
import importlib as imp
+ imp.reload(archipack_progressbar)
+ imp.reload(archipack_material)
imp.reload(archipack_snap)
imp.reload(archipack_manipulator)
imp.reload(archipack_reference_point)
@@ -54,6 +56,7 @@ if "bpy" in locals():
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)
@@ -62,6 +65,8 @@ if "bpy" in locals():
print("archipack: reload ready")
else:
+ from . import archipack_progressbar
+ from . import archipack_material
from . import archipack_snap
from . import archipack_manipulator
from . import archipack_reference_point
@@ -70,6 +75,7 @@ else:
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
@@ -78,7 +84,6 @@ else:
print("archipack: ready")
-
# noinspection PyUnresolvedReferences
import bpy
# noinspection PyUnresolvedReferences
@@ -135,7 +140,7 @@ class Archipack_Pref(AddonPreferences):
)
max_style_draw_tool = BoolProperty(
name="Draw a wall use 3dsmax style",
- description="Reverse clic / release cycle for Draw a wall",
+ description="Reverse clic / release & drag cycle for Draw a wall",
default=True
)
# Arrow sizes (world units)
@@ -151,6 +156,11 @@ class Archipack_Pref(AddonPreferences):
min=2,
default=10
)
+ matlib_path = StringProperty(
+ name="Folder path",
+ description="absolute path to material library folder",
+ default=""
+ )
# Font sizes and basic colour scheme
feedback_size_main = IntProperty(
name="Main",
@@ -225,6 +235,11 @@ class Archipack_Pref(AddonPreferences):
box.prop(self, "max_style_draw_tool")
box = layout.box()
row = box.row()
+ col = row.column()
+ col.label(text="Material library:")
+ col.prop(self, "matlib_path")
+ box = layout.box()
+ row = box.row()
split = row.split(percentage=0.5)
col = split.column()
col.label(text="Colors:")
@@ -296,6 +311,7 @@ class TOOLS_PT_Archipack_Create(Panel):
def draw(self, context):
global icons_collection
+
icons = icons_collection["main"]
layout = self.layout
row = layout.row(align=True)
@@ -356,10 +372,22 @@ class TOOLS_PT_Archipack_Create(Panel):
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"
# ----------------------------------------------------
@@ -400,13 +428,16 @@ def draw_menu(self, context):
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_create_menu(Menu):
bl_label = 'Archipack'
bl_idname = 'ARCHIPACK_create_menu'
- bl_context = "objectmode"
def draw(self, context):
draw_menu(self, context)
@@ -454,6 +485,8 @@ def register():
icons.load(name, os.path.join(icons_dir, icon), 'IMAGE')
icons_collection["main"] = icons
+ archipack_progressbar.register()
+ archipack_material.register()
archipack_snap.register()
archipack_manipulator.register()
archipack_reference_point.register()
@@ -462,6 +495,7 @@ def register():
archipack_window.register()
archipack_stair.register()
archipack_wall2.register()
+ archipack_roof.register()
archipack_slab.register()
archipack_fence.register()
archipack_truss.register()
@@ -484,8 +518,8 @@ def unregister():
bpy.utils.unregister_class(TOOLS_PT_Archipack_Tools)
bpy.utils.unregister_class(TOOLS_PT_Archipack_Create)
bpy.utils.unregister_class(Archipack_Pref)
-
- # unregister subs
+ archipack_progressbar.unregister()
+ archipack_material.unregister()
archipack_snap.unregister()
archipack_manipulator.unregister()
archipack_reference_point.unregister()
@@ -494,6 +528,7 @@ def unregister():
archipack_window.unregister()
archipack_stair.unregister()
archipack_wall2.unregister()
+ archipack_roof.unregister()
archipack_slab.unregister()
archipack_fence.unregister()
archipack_truss.unregister()
diff --git a/archipack/archipack_2d.py b/archipack/archipack_2d.py
index 912e3cb8..fcd578da 100644
--- a/archipack/archipack_2d.py
+++ b/archipack/archipack_2d.py
@@ -118,6 +118,10 @@ class Line(Projection):
self.v = Vector((0, 0))
@property
+ def copy(self):
+ return Line(self.p.copy(), self.v.copy())
+
+ @property
def p0(self):
return self.p
@@ -250,6 +254,20 @@ class Line(Projection):
t = (c * (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 * c
+ if d == 0:
+ return False, 0, 0, 0
+ dp = line.p - self.p
+ c2 = self.cross_z
+ u = (c * dp) / d
+ v = (c2 * 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
@@ -258,6 +276,8 @@ class Line(Projection):
"""
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 * dp) / (dl * dl)
return t > 0 and t < 1, d, t
@@ -318,7 +338,19 @@ class Line(Projection):
Draw Line with open gl in screen space
aka: coords are in pixels
"""
- raise NotImplementedError
+ 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.objects.link(curve_obj)
+ curve_obj.select = True
def make_offset(self, offset, last=None):
"""
@@ -581,6 +613,16 @@ class Arc(Circle):
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))
diff --git a/archipack/archipack_autoboolean.py b/archipack/archipack_autoboolean.py
index a171532c..f304ced0 100644
--- a/archipack/archipack_autoboolean.py
+++ b/archipack/archipack_autoboolean.py
@@ -28,16 +28,16 @@ import bpy
from bpy.types import Operator
from bpy.props import EnumProperty
from mathutils import Vector
-from .materialutils import MaterialUtils
-
-from os import path
+"""
+from os import path
def debug_using_gl(context, filename):
context.scene.update()
temp_path = "C:\\tmp\\"
context.scene.render.filepath = path.join(temp_path, filename + ".png")
bpy.ops.render.opengl(write_still=True)
+"""
class ArchipackBoolManager():
@@ -95,7 +95,7 @@ class ArchipackBoolManager():
'archipack_robusthole' in wall or
'archipack_handle' in wall)
- def datablock(self, o):
+ def datablock(self, o, basis='WALL'):
"""
get datablock from windows and doors
return
@@ -104,10 +104,14 @@ class ArchipackBoolManager():
"""
d = None
if o.data:
- if "archipack_window" in o.data:
- d = o.data.archipack_window[0]
- elif "archipack_door" in o.data:
- d = o.data.archipack_door[0]
+ if basis == 'WALL':
+ if "archipack_window" in o.data:
+ d = o.data.archipack_window[0]
+ elif "archipack_door" in o.data:
+ d = o.data.archipack_door[0]
+ elif basis == 'ROOF':
+ if "archipack_roof" in o.data:
+ d = o.data.archipack_roof[0]
return d
def prepare_hole(self, hole):
@@ -131,7 +135,7 @@ class ArchipackBoolManager():
return hole
return None
- def _generate_hole(self, context, o):
+ def _generate_hole(self, context, o, basis='WALL'):
# use existing one
if self.mode != 'ROBUST':
hole = self.get_child_hole(o)
@@ -139,7 +143,7 @@ class ArchipackBoolManager():
# print("_generate_hole Use existing hole %s" % (hole.name))
return hole
# generate single hole from archipack primitives
- d = self.datablock(o)
+ d = self.datablock(o, basis)
hole = None
if d is not None:
if (self.itM is not None and (
@@ -224,7 +228,9 @@ class ArchipackBoolManager():
if wall.parent is not None:
hole_obj.parent = wall.parent
hole_obj.matrix_world = wall.matrix_world.copy()
- MaterialUtils.add_wall2_materials(hole_obj)
+ 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):
@@ -268,10 +274,10 @@ class ArchipackBoolManager():
hole_obj = self.create_merge_basis(context, wall)
else:
hole_obj = m.object
- # debug_using_gl(context, "260")
+
m.object = hole_obj
self.prepare_hole(hole_obj)
- # debug_using_gl(context, "263")
+
to_delete = []
# mixed-> mixed
@@ -284,12 +290,11 @@ class ArchipackBoolManager():
# remove modifier and holes not found in new list
self.remove_modif_and_object(context, hole_obj, to_delete)
- # debug_using_gl(context, "276")
+
# add modifier and holes not found in existing
for h in holes:
if h not in existing:
self.union(hole_obj, h)
- # debug_using_gl(context, "281")
# Interactive
def update_interactive(self, context, wall, childs, holes):
@@ -385,6 +390,16 @@ class ArchipackBoolManager():
elif modif is not None:
wall.modifiers.remove(modif)
+ def get_basis_type(self, o):
+ if o.data is not None:
+ if "archipack_wall2" in o.data:
+ return 'WALL'
+ elif "archipack_roof" in o.data:
+ return 'ROOF'
+ elif "archipack_wall" in o.data:
+ return 'WALL'
+ return 'DEFAULT'
+
def autoboolean(self, context, wall):
"""
Entry point for multi-boolean operations like
@@ -397,19 +412,26 @@ class ArchipackBoolManager():
# get wall bounds to find what's inside
self._get_bounding_box(wall)
+ # filter roofs when wall is roof
+ basis = self.get_basis_type(wall)
+
# either generate hole or get existing one
for o in context.scene.objects:
- h = self._generate_hole(context, o)
+ h = self._generate_hole(context, o, basis)
if h is not None:
holes.append(h)
childs.append(o)
- # debug_using_gl(context, "395")
+
self.sort_holes(wall, holes)
# hole(s) are selected and active after this one
for hole in holes:
+ # copy wall material to hole
+ hole.data.materials.clear()
+ for mat in wall.data.materials:
+ hole.data.materials.append(mat)
+
self.prepare_hole(hole)
- # debug_using_gl(context, "401")
# update / remove / add boolean modifier
if self.mode == 'INTERACTIVE':
@@ -430,20 +452,18 @@ class ArchipackBoolManager():
else:
wall.parent.select = True
context.scene.objects.active = wall.parent
- # debug_using_gl(context, "422")
+
wall.select = True
for o in childs:
if 'archipack_robusthole' in o:
o.hide_select = False
o.select = True
- # debug_using_gl(context, "428")
bpy.ops.archipack.parent_to_reference()
for o in childs:
if 'archipack_robusthole' in o:
o.hide_select = True
- # debug_using_gl(context, "435")
def detect_mode(self, context, wall):
for m in wall.modifiers:
@@ -461,9 +481,12 @@ class ArchipackBoolManager():
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)
+ basis = self.get_basis_type(wall)
+ d = self.datablock(o, basis)
+
hole = None
hole_obj = None
# default mode defined by __init__
@@ -477,6 +500,10 @@ class ArchipackBoolManager():
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':
@@ -518,11 +545,13 @@ class ArchipackBoolManager():
bpy.ops.archipack.parent_to_reference()
wall.select = True
context.scene.objects.active = wall
- d = wall.data.archipack_wall2[0]
- g = d.get_generator()
- d.setup_childs(wall, g)
- d.relocate_childs(context, wall, g)
-
+ if "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)
@@ -559,7 +588,9 @@ class ARCHIPACK_OT_single_boolean(Operator):
def poll(cls, context):
w = context.active_object
return (w.data is not None and
- "archipack_wall2" in w.data and
+ ("archipack_wall2" in w.data or
+ "archipack_wall" in w.data or
+ "archipack_roof" in w.data) and
len(context.selected_objects) == 2
)
@@ -648,14 +679,18 @@ class ARCHIPACK_OT_generate_hole(Operator):
if context.mode == "OBJECT":
manager = ArchipackBoolManager(mode='HYBRID')
o = context.active_object
- d = manager.datablock(o)
+
+ # filter roofs when o is roof
+ basis = manager.get_basis_type(o)
+
+ d = manager.datablock(o, basis)
if d is None:
- self.report({'WARNING'}, "Archipack: active object must be a door or a window")
+ 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 = True
context.scene.objects.active = o
- hole = manager._generate_hole(context, o)
+ hole = manager._generate_hole(context, o, basis)
manager.prepare_hole(hole)
hole.select = False
o.select = True
diff --git a/archipack/archipack_cutter.py b/archipack/archipack_cutter.py
new file mode 100644
index 00000000..bce82008
--- /dev/null
+++ b/archipack/archipack_cutter.py
@@ -0,0 +1,910 @@
+# -*- 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 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
+
+ @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 += offset * self.cross_z.normalized()
+ 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, dont 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 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((100 * self.xsize, 0.1)))
+ 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]
+ 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 realy 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
+ Dosen'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
+
+ if not slice_res:
+ # print("slice fails")
+ # found more segments than input
+ # cutter made more than one loop
+ return
+
+ if len(store) < 1:
+ if is_inside:
+ # print("not touching, add as hole")
+ self.holes.append(cutter)
+ return
+
+ self.segs = store
+ self.is_convex()
+
+
+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']
+ p0 = s.p0 + s.cross_z.normalized() * of
+ self.bissect(bm, p0.to_3d(), s.cross_z.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:
+ self.bissect(bm, s.p0.to_3d(), s.cross_z.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=5)
+
+ 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']
+ p0 = s.p0 + s.cross_z.normalized() * of
+ self.bissect(bm, p0.to_3d(), s.cross_z.to_3d(), clear_outer=cutable.convex)
+ else:
+ for s in cutable.segs:
+ if s.length > 0:
+ self.bissect(bm, s.p0.to_3d(), s.cross_z.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=5)
+
+
+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 = BoolProperty(
+ description="keep closed to be wall snap manipulator compatible",
+ options={'SKIP_SAVE'},
+ default=True
+ )
+
+ def draw(self, layout, context):
+ box = layout.box()
+ row = box.row()
+ if self.parts_expand:
+ row.prop(self, 'parts_expand', icon="TRIA_DOWN", icon_only=True, text="Parts", emboss=False)
+ box.prop(self, 'n_parts')
+ for i, part in enumerate(self.parts):
+ if i < self.n_parts:
+ part.draw(layout, context, i)
+ else:
+ row.prop(self, 'parts_expand', icon="TRIA_RIGHT", icon_only=True, 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([
+ [1, 0, 0, pt.x],
+ [0, 1, 0, pt.y],
+ [0, 0, 1, pt.z],
+ [0, 0, 0, 1]
+ ])
+ 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)
+ 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.active_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
index f29c44d1..ffc1e4c4 100644
--- a/archipack/archipack_door.py
+++ b/archipack/archipack_door.py
@@ -37,7 +37,6 @@ from mathutils import Vector
# door component objects (panels, handles ..)
from .bmesh_utils import BmeshEdit as bmed
from .panel import Panel as DoorPanel
-from .materialutils import MaterialUtils
from .archipack_handle import create_handle, door_handle_horizontal_01
from .archipack_manipulator import Manipulable
from .archipack_preset import ArchipackPreset, PresetMenuOperator
@@ -549,7 +548,7 @@ class archipack_door_panel(ArchipackObject, PropertyGroup):
if handle is None:
m = bpy.data.meshes.new("Handle")
handle = create_handle(context, o, m)
- MaterialUtils.add_handle_materials(handle)
+
verts, faces = door_handle_horizontal_01(self.direction, 1)
b_verts, b_faces = door_handle_horizontal_01(self.direction, 0, offset=len(verts))
b_verts = [(v[0], v[1] - self.y, v[2]) for v in b_verts]
@@ -698,6 +697,9 @@ class ARCHIPACK_OT_door_panel(Operator):
),
default='BOTH'
)
+ material = StringProperty(
+ default=""
+ )
def draw(self, context):
layout = self.layout
@@ -736,8 +738,11 @@ class ARCHIPACK_OT_door_panel(Operator):
o.lock_scale[2] = True
o.select = True
context.scene.objects.active = o
+ m = o.archipack_material.add()
+ m.category = "door"
+ m.material = self.material
d.update(context)
- MaterialUtils.add_door_materials(o)
+ # MaterialUtils.add_door_materials(o)
o.lock_rotation[0] = True
o.lock_rotation[1] = True
return o
@@ -1049,8 +1054,14 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
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)
+ 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()
@@ -1062,9 +1073,16 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
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)
+ 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
@@ -1123,7 +1141,9 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
p.lock_scale[2] = True
p.parent = linked
p.matrix_world = linked.matrix_world.copy()
- p.location = child.location.copy()
+ m = p.archipack_material.add()
+ m.category = 'door'
+ m.material = o.archipack_material[0].material
else:
p = l_childs[order[i]]
@@ -1135,7 +1155,7 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
if handle is not None:
if h is None:
h = create_handle(context, p, handle.data)
- MaterialUtils.add_handle_materials(h)
+ # MaterialUtils.add_handle_materials(h)
h.location = handle.location.copy()
elif h is not None:
context.scene.objects.unlink(h)
@@ -1223,7 +1243,8 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
panels_distrib=self.panels_distrib,
panels_x=self.panels_x,
panels_y=self.panels_y,
- handle=handle
+ handle=handle,
+ material=o.archipack_material[0].material
)
child = context.active_object
# parenting at 0, 0, 0 before set object matrix_world
@@ -1297,7 +1318,11 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
hole_obj['archipack_hole'] = True
hole_obj.parent = o
hole_obj.matrix_world = o.matrix_world.copy()
- MaterialUtils.add_wall2_materials(hole_obj)
+
+ 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))
@@ -1324,7 +1349,7 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
matids = hole.mat(16, 0, 1, path_type='RECTANGLE')
uvs = hole.uv(16, v, v, size, v, 0, 0, 0, 0, path_type='RECTANGLE')
bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs)
- MaterialUtils.add_wall2_materials(o)
+
o.select = True
context.scene.objects.active = o
return o
@@ -1534,8 +1559,8 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator):
context.scene.objects.link(o)
o.select = True
context.scene.objects.active = o
- self.load_preset(d)
self.add_material(o)
+ self.load_preset(d)
o.select = True
context.scene.objects.active = o
return o
@@ -1811,7 +1836,6 @@ class ARCHIPACK_OT_door_preset(ArchipackPreset, Operator):
@property
def blacklist(self):
- # 'x', 'y', 'z', 'direction',
return ['manipulators']
diff --git a/archipack/archipack_floor.py b/archipack/archipack_floor.py
index 7f02c2dd..29957716 100644
--- a/archipack/archipack_floor.py
+++ b/archipack/archipack_floor.py
@@ -14,826 +14,1221 @@
#
# 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.
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# ----------------------------------------------------------
-# Base code inspired by JARCH Vis
-# Original Author: Jacob Morris
-# Author : Stephen Leger (s-leger)
+# Author: Jacob Morris - Stephen Leger (s-leger)
# ----------------------------------------------------------
import bpy
from bpy.types import Operator, PropertyGroup, Mesh, Panel
from bpy.props import (
- BoolProperty, EnumProperty, FloatProperty,
- IntProperty, CollectionProperty
+ FloatProperty, CollectionProperty, StringProperty,
+ BoolProperty, IntProperty, EnumProperty
)
-from random import uniform, randint
-from math import tan, pi, sqrt
-from mathutils import Vector
+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_manipulator import Manipulable
+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
+ )
-def create_flooring(if_tile, over_width, over_length, b_width, b_length, b_length2, is_length_vary,
- length_vary, num_boards, space_l, space_w, spacing, t_width, t_length, is_offset, offset,
- is_ran_offset, offset_vary, t_width2, is_width_vary, width_vary, max_boards, is_ran_thickness,
- ran_thickness, th, hb_dir):
-
- # create siding
- if if_tile == "1": # Tiles Regular
- return tile_regular(over_width, over_length, t_width, t_length, spacing, is_offset, offset,
- is_ran_offset, offset_vary, th)
- elif if_tile == "2": # Large + Small
- return tile_ls(over_width, over_length, t_width, t_length, spacing, th)
- elif if_tile == "3": # Large + Many Small
- return tile_lms(over_width, over_length, t_width, spacing, th)
- elif if_tile == "4": # Hexagonal
- return tile_hexagon(over_width, over_length, t_width2, spacing, th)
- elif if_tile == "21": # Planks
- return wood_regular(over_width, over_length, b_width, b_length, space_l, space_w,
- is_length_vary, length_vary,
- is_width_vary, width_vary,
- is_offset, offset,
- is_ran_offset, offset_vary,
- max_boards, is_ran_thickness,
- ran_thickness, th)
- elif if_tile == "22": # Parquet
- return wood_parquet(over_width, over_length, b_width, spacing, num_boards, th)
- elif if_tile == "23": # Herringbone Parquet
- return wood_herringbone(over_width, over_length, b_width, b_length2, spacing, th, hb_dir, True)
- elif if_tile == "24": # Herringbone
- return wood_herringbone(over_width, over_length, b_width, b_length2, spacing, th, hb_dir, False)
-
- return [], []
-
-
-def wood_herringbone(ow, ol, bw, bl, s, th, hb_dir, stepped):
- verts = []
- faces = []
- an_45 = 0.5 * sqrt(2)
- x, y, z = 0.0, 0.0, th
- x_off, y_off = 0.0, 0.0 # used for finding farther forwards points when stepped
- ang_s = s * an_45
- s45 = s / an_45
-
- # step variables
- if stepped:
- x_off = an_45 * bw
- y_off = an_45 * bw
-
- wid_off = an_45 * bl # offset from one end of the board to the other inline with width
- len_off = an_45 * bl # offset from one end of the board to the other inline with length
- w = bw / an_45 # width adjusted for 45 degree rotation
-
- # figure out starting position
- if hb_dir == "1":
- y = -wid_off
-
- elif hb_dir == "2":
- x = ow
- y = ol + wid_off
-
- elif hb_dir == "3":
- x = -wid_off
- y = ol
-
- elif hb_dir == "4":
- x = ow + wid_off
-
- # loop going forwards
- while (hb_dir == "1" and y < ol + wid_off) or (hb_dir == "2" and y > 0 - wid_off) or \
- (hb_dir == "3" and x < ow + wid_off) or (hb_dir == "4" and x > 0 - wid_off):
- going_forwards = True
-
- # loop going right
- while (hb_dir == "1" and x < ow) or (hb_dir == "2" and x > 0) or (hb_dir == "3" and y > 0 - y_off) or \
- (hb_dir == "4" and y < ol + y_off):
- p = len(verts)
-
- # add verts
- # forwards
- verts.append((x, y, z))
-
- if hb_dir == "1":
-
- if stepped and x != 0:
- verts.append((x - x_off, y + y_off, z))
- else:
- verts.append((x, y + w, z))
-
- if going_forwards:
- y += wid_off
- else:
- y -= wid_off
- x += len_off
-
- verts.append((x, y, z))
- if stepped:
- verts.append((x - x_off, y + y_off, z))
- x -= x_off - ang_s
- if going_forwards:
- y += y_off + ang_s
- else:
- y -= y_off + ang_s
- else:
- verts.append((x, y + w, z))
- x += s
-
- # backwards
- elif hb_dir == "2":
-
- if stepped and x != ow:
- verts.append((x + x_off, y - y_off, z))
- else:
- verts.append((x, y - w, z))
-
- if going_forwards:
- y -= wid_off
- else:
- y += wid_off
- x -= len_off
-
- verts.append((x, y, z))
- if stepped:
- verts.append((x + x_off, y - y_off, z))
- x += x_off - ang_s
- if going_forwards:
- y -= y_off + ang_s
- else:
- y += y_off + ang_s
- else:
- verts.append((x, y - w, z))
- x -= s
- # right
- elif hb_dir == "3":
-
- if stepped and y != ol:
- verts.append((x + y_off, y + x_off, z))
- else:
- verts.append((x + w, y, z))
-
- if going_forwards:
- x += wid_off
- else:
- x -= wid_off
- y -= len_off
-
- verts.append((x, y, z))
- if stepped:
- verts.append((x + y_off, y + x_off, z))
- y += x_off - ang_s
- if going_forwards:
- x += y_off + ang_s
- else:
- x -= y_off + ang_s
- else:
- verts.append((x + w, y, z))
- y -= s
- # left
- else:
-
- if stepped and y != 0:
- verts.append((x - y_off, y - x_off, z))
- else:
- verts.append((x - w, y, z))
-
- if going_forwards:
- x -= wid_off
- else:
- x += wid_off
- y += len_off
-
- verts.append((x, y, z))
- if stepped:
- verts.append((x - y_off, y - x_off, z))
- y -= x_off - ang_s
- if going_forwards:
- x -= y_off + ang_s
- else:
- x += y_off + ang_s
- else:
- verts.append((x - w, y, z))
- y += s
-
- # faces
- faces.append((p, p + 2, p + 3, p + 1))
-
- # flip going_right
- going_forwards = not going_forwards
- x_off *= -1
-
- # if not in forwards position, then move back before adjusting values for next row
- if not going_forwards:
- x_off = abs(x_off)
- if hb_dir == "1":
- y -= wid_off
- if stepped:
- y -= y_off + ang_s
- elif hb_dir == "2":
- y += wid_off
- if stepped:
- y += y_off + ang_s
- elif hb_dir == "3":
- x -= wid_off
- if stepped:
- x -= y_off + ang_s
- else:
- x += wid_off
- if stepped:
- x += y_off + ang_s
-
- # adjust forwards
- if hb_dir == "1":
- y += w + s45
- x = 0
- elif hb_dir == "2":
- y -= w + s45
- x = ow
- elif hb_dir == "3":
- x += w + s45
- y = ol
- else:
- x -= w + s45
- y = 0
-
- return verts, faces
-
+# ------------------------------------------------------------------
+# Define property class to store object parameters and update mesh
+# ------------------------------------------------------------------
-def tile_ls(ow, ol, tw, tl, s, z):
- """
- pattern:
- _____
- | |_|
- |___|
- x and y are axis of big one
- """
+class Floor():
- verts = []
- faces = []
+ def __init__(self):
+ # self.colour_inactive = (1, 1, 1, 1)
+ pass
- # big half size
- hw = (tw / 2) - (s / 2)
- hl = (tl / 2) - (s / 2)
- # small half size
- hws = (tw / 4) - (s / 2)
- hls = (tl / 4) - (s / 2)
+ def set_offset(self, offset, last=None):
+ """
+ Offset line and compute intersection point
+ between segments
+ """
+ self.line = self.make_offset(offset, last)
- # small, offset from big x,y
- xo = 0.75 * tw
- yo = 0.25 * tl
+ def straight_floor(self, a0, length):
+ s = self.straight(length).rotate(a0)
+ return StraightFloor(s.p, s.v)
- # offset for pattern
- rx = 2.5 * tw
- ry = 0.5 * tl
+ 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)
- # width and a half of big
- ow_x = ow + 0.5 * tw
- ol_y = ol + 0.5 * tl
- # start pattern with big one
- x = tw
- y = -tl
+class StraightFloor(Floor, Line):
- while y < ol_y:
+ def __init__(self, p, v):
+ Line.__init__(self, p, v)
+ Floor.__init__(self)
- while x < ow_x:
- p = len(verts)
+class CurvedFloor(Floor, Arc):
- # Large
- x0 = max(0, x - hw)
- y0 = max(0, y - hl)
- x1 = min(ow, x + hw)
- y1 = min(ol, y + hl)
- if y1 > 0:
- if x1 > 0 and x0 < ow and y0 < ol:
+ def __init__(self, c, radius, a0, da):
+ Arc.__init__(self, c, radius, a0, da)
+ Floor.__init__(self)
- verts.extend([(x0, y1, z), (x1, y1, z), (x1, y0, z), (x0, y0, z)])
- faces.append((p, p + 1, p + 2, p + 3))
- p = len(verts)
- # Small
- x0 = x + xo - hws
- y0 = y + yo - hls
- x1 = min(ow, x + xo + hws)
+class FloorGenerator(CutAblePolygon, CutAbleGenerator):
- if x1 > 0 and x0 < ow and y0 < ol:
+ def __init__(self, parts):
+ self.parts = parts
+ self.segs = []
+ self.holes = []
+ self.convex = True
+ self.xsize = 0
- y1 = min(ol, y + yo + hls)
- verts.extend([(x0, y1, z), (x1, y1, z), (x1, y0, z), (x0, y0, z)])
- faces.append((p, p + 1, p + 2, p + 3))
+ def add_part(self, part):
- x += rx
-
- y += ry
- x = x % rx - tw
- if x < -tw:
- x += rx
-
- return verts, faces
+ 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]
+ 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.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
-def tile_hexagon(ow, ol, tw, s, z):
- verts = []
- faces = []
- offset = False
+ 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
- w = tw / 2
- y = 0.0
- h = w * tan(pi / 6)
- r = sqrt((w * w) + (h * h))
+ self.top = d.thickness
- while y < ol + tw:
- if not offset:
- x = 0.0
- else:
- x = w + (s / 2)
+ 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)
- while x < ow + tw:
- p = len(verts)
+ self.cut_holes(bm, self)
+ self.cut_boundary(bm, self)
- verts.extend([(x + w, y + h, z), (x, y + r, z), (x - w, y + h, z),
- (x - w, y - h, z), (x, y - r, z), (x + w, y - h, z)])
- faces.extend([(p, p + 1, p + 2, p + 3), (p + 3, p + 4, p + 5, p)])
+ bmesh.ops.dissolve_limit(bm,
+ angle_limit=0.01,
+ use_dissolve_boundaries=False,
+ verts=bm.verts,
+ edges=bm.edges,
+ delimit=1)
- x += tw + s
+ bm.verts.ensure_lookup_table()
- y += r + h + s
- offset = not offset
+ # 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
- return verts, faces
+ # 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=0,
+ 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=1)
+
+ 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
-def tile_lms(ow, ol, tw, s, z):
- verts = []
- faces = []
- small = True
+ self.add_plane(d, verts, faces, matids, uvs, x + tw + sp, y + s_tl + sp, s_tw, s_tl) # small one
- y = 0.0
- ref = (tw - s) / 2
+ if step_back:
+ x += tw + sp
+ y -= s_tl + sp
+ else:
+ x += tw + s_tw + 2 * sp
+ y += s_tl + sp
- while y < ol:
- x = 0.0
- large = False
- while x < ow:
- if small:
- x1 = min(x + ref, ow)
- y1 = min(y + ref, ol)
- p = len(verts)
- verts.extend([(x, y1, z), (x, y, z)])
- verts.extend([(x1, y1, z), (x1, y, z)])
- faces.append((p, p + 1, p + 3, p + 2))
- x += ref
- else:
- if not large:
- x1 = min(x + ref, ow)
- for i in range(2):
- y0 = y + i * (ref + s)
- if x < ow and y0 < ol:
- y1 = min(y0 + ref, ol)
- p = len(verts)
- verts.extend([(x, y1, z), (x, y0, z)])
- verts.extend([(x1, y1, z), (x1, y0, z)])
- faces.append((p, p + 1, p + 3, p + 2))
- x += ref
+ step_back = not step_back
else:
- x1 = min(x + tw, ow)
- y1 = min(y + tw, ol)
- p = len(verts)
- verts.extend([(x, y1, z), (x, y, z)])
- verts.extend([(x1, y1, z), (x1, y, z)])
- faces.append((p, p + 1, p + 3, p + 2))
- x += tw
- large = not large
- x += s
- if small:
- y += ref + s
- else:
- y += tw + s
- small = not small
-
- return verts, faces
+ 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
-def tile_regular(ow, ol, tw, tl, s, is_offset, offset, is_ran_offset, offset_vary, z):
- verts = []
- faces = []
- off = False
- o = 1 / (100 / offset)
- y = 0.0
+ while x - width / 2 < self.xmax: # place tile as long as left is still within bounds
+ f = len(verts)
- while y < ol:
+ 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
- tw2 = 0
- if is_offset:
- if is_ran_offset:
- v = tw * 0.0049 * offset_vary
- tw2 = uniform((tw / 2) - v, (tw / 2) + v)
- elif off:
- tw2 = o * tw
- x = -tw2
- y1 = min(ol, y + tl)
+ 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
- while x < ow:
- p = len(verts)
- x0 = max(0, x)
- x1 = min(ow, x + tw)
+ orient_length = not orient_length
- verts.extend([(x0, y1, z), (x0, y, z), (x1, y, z), (x1, y1, z)])
- faces.append((p, p + 1, p + 2, p + 3))
- x = x1 + s
+ start_orient_length = not start_orient_length
+ x += bl + d.spacing
- y += tl + s
- off = not off
+ 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))
- return verts, faces
+ y = self.ymin - y_dif
+ while y < self.ymax:
+ x = self.xmin
+ while x < self.xmax:
+ # left side
-def wood_parquet(ow, ol, bw, s, num_boards, z):
- verts = []
- faces = []
- x = 0.0
+ 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))
- start_orient_length = True
+ x += x_dif + d.spacing
- # figure board length
- bl = (bw * num_boards) + (s * (num_boards - 1))
- while x < ow:
+ # 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 = 0.0
+ y += width_dif + sp_dif # adjust spacing amount for 45 degree angle
- orient_length = start_orient_length
+ def herringbone_parquet(self, d, verts, faces, matids, uvs):
+ """
+ Boards are at 45 degree angle, in chevron pattern, ends are square, not angled
+ """
- while y < ol:
+ an_45 = 0.5 * sqrt(2)
- if orient_length:
- y0 = y
- y1 = min(y + bl, ol)
+ 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
- for i in range(num_boards):
+ 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
- bx = x + i * (bw + s)
+ 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
- if bx < ow and y < ol:
+ while x - x_dif_45 < self.xmax: # continue as long as top left corner is still good
+ # left side
- # make sure board should be placed
- x0 = bx
- x1 = min(bx + bw, ow)
+ 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))
- p = len(verts)
- verts.extend([(x0, y0, z), (x1, y0, z), (x1, y1, z), (x0, y1, z)])
- faces.append((p, p + 1, p + 2, p + 3))
+ x += x_dif - x_dif_45 + sp_dif
+ y0 = y + y_dif - y_dif_45 - sp_dif
- else:
- x0 = x
- x1 = min(x + bl, ow)
+ 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))
- for i in range(num_boards):
+ x += x_dif + x_dif_45 + sp_dif
- by = y + i * (bw + s)
+ else: # we didn't place the right board, so step ahead far enough the the while loop for x breaks
+ break
- if x < ow and by < ol:
- y0 = by
- y1 = min(by + bw, ol)
- p = len(verts)
+ y += width_dif + (2 * sp_dif)
- verts.extend([(x0, y0, z), (x1, y0, z), (x1, y1, z), (x0, y1, z)])
- faces.append((p, p + 1, p + 2, p + 3))
+ 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
- y += bl + s
+ 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):
+ # clear data before refreshing it
+
+ self.uv_factor = 1 / max(self.xmax, self.ymax) # automatically scale to keep within reasonable bounds
+
+ 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)
- orient_length = not orient_length
- start_orient_length = not start_orient_length
+def update(self, context):
+ self.update(context)
- x += bl + s
- return verts, faces
+def update_type(self, context):
+ d = self.find_in_selection(context)
-def wood_regular(ow, ol, bw, bl, s_l, s_w,
- is_length_vary, length_vary,
- is_width_vary, width_vary,
- is_offset, offset,
- is_ran_offset, offset_vary,
- max_boards, is_r_h,
- r_h, th):
- verts = []
- faces = []
- x = 0.0
- row = 0
- while x < ow:
+ if d is not None and d.auto_update:
- if is_width_vary:
- v = bw * (width_vary / 100) * 0.499
- bw2 = uniform(bw / 2 - v, bw / 2 + v)
- else:
- bw2 = bw
+ d.auto_update = False
+ # find part index
+ idx = 0
+ for i, part in enumerate(d.parts):
+ if part == self:
+ idx = i
+ break
- x1 = min(x + bw2, ow)
- if is_offset:
- if is_ran_offset:
- v = bl * (offset_vary / 100) * 0.5
- y = -uniform(bl / 2 - v, bl / 2 + v)
+ 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:
- y = -(row % 2) * bl * (offset / 100)
+ w = w0.curved_floor(part.a0, part.da, part.radius)
+ else:
+ g = FloorGenerator(None)
+ g.add_part(self)
+ w = g.segs[0]
+
+ # w0 - w - w1
+ dp = w.p1 - w.p0
+ if "C_" in self.type:
+ part.radius = 0.5 * dp.length
+ part.da = pi
+ a0 = atan2(dp.y, dp.x) - pi / 2 - a0
else:
- y = 0
+ 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)
- row += 1
- counter = 1
+ if a0 > pi:
+ a0 -= 2 * pi
+ if a0 < -pi:
+ a0 += 2 * pi
+ part1.a0 = a0
- while y < ol:
+ d.auto_update = True
- z = th
- if is_r_h:
- v = z * 0.5 * (r_h / 100)
- z = uniform(z / 2 - v, z / 2 + v)
+def update_manipulators(self, context):
+ self.update(context, manipulable_refresh=True)
- bl2 = bl
- if is_length_vary:
- if (counter >= max_boards):
- bl2 = ol
- else:
- v = bl * (length_vary / 100) * 0.5
- bl2 = uniform(bl / 2 - v, bl / 2 + v)
+def update_path(self, context):
+ self.update_path(context)
- y0 = max(0, y)
- y1 = min(y + bl2, ol)
- if y1 > y0:
- p = len(verts)
+class archipack_floor_part(PropertyGroup):
- verts.extend([(x, y0, z), (x1, y0, z), (x1, y1, z), (x, y1, z)])
- faces.append((p, p + 1, p + 2, p + 3))
+ """
+ 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="Add to current segment offset",
+ 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 = [o for o in 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))
- y += bl2 + s_l
+ if self.type in ['C_SEG']:
+ box.prop(self, "radius")
+ box.prop(self, "da")
+ else:
+ box.prop(self, "length")
+ box.prop(self, "a0")
- counter += 1
- x += bw2 + s_w
+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
+ )
- return verts, faces
+ 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='Vary Thickness',
+ description='Vary board thickness?',
+ default=False,
+ update=update
+ )
+ thickness_variance = FloatProperty(
+ name='Thickness Variance',
+ description='How much board thickness can vary by',
+ min=0, max=100,
+ default=25,
+ precision=2,
+ subtype='PERCENTAGE',
+ update=update
+ )
+ board_width = FloatProperty(
+ name='Board Width',
+ description='The width of the boards',
+ unit='LENGTH', subtype='DISTANCE',
+ min=0.02,
+ default=0.2,
+ precision=2,
+ update=update
+ )
+ vary_width = BoolProperty(
+ name='Vary Width',
+ description='Vary board width',
+ default=False,
+ update=update
+ )
+ width_variance = FloatProperty(
+ name='Width Variance',
+ description='How much board width can 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
+ )
-def tile_grout(ow, ol, depth, th):
- z = min(th - 0.001, max(0.001, th - depth))
- x = ow
- y = ol
+ board_length = FloatProperty(
+ name='Board 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='Board Length',
+ description='The length of the boards',
+ unit='LENGTH', subtype='DISTANCE',
+ precision=2,
+ min=0.02,
+ default=2,
+ update=update
+ )
+ vary_length = BoolProperty(
+ name='Vary Length',
+ description='Vary board length',
+ default=False,
+ update=update
+ )
+ length_variance = FloatProperty(
+ name='Length 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
+ )
- verts = [(0.0, 0.0, 0.0), (0.0, 0.0, z), (x, 0.0, z), (x, 0.0, 0.0),
- (0.0, y, 0.0), (0.0, y, z), (x, y, z), (x, y, 0.0)]
+ # parquet specific
+ boards_in_group = IntProperty(
+ name='Boards in Group',
+ description='Number of boards in a group',
+ min=1, default=4,
+ update=update
+ )
- faces = [(0, 3, 2, 1), (4, 5, 6, 7), (0, 1, 5, 4),
- (1, 2, 6, 5), (3, 7, 6, 2), (0, 4, 7, 3)]
+ # tile specific
+ tile_width = FloatProperty(
+ name='Tile Width',
+ description='Width of the tiles',
+ unit='LENGTH', subtype='DISTANCE',
+ min=0.002,
+ default=0.2,
+ precision=2,
+ update=update
+ )
+ tile_length = FloatProperty(
+ name='Tile Length',
+ description='Length of the tiles',
+ unit='LENGTH', subtype='DISTANCE',
+ precision=2,
+ min=0.02,
+ default=0.3,
+ update=update
+ )
- return verts, faces
+ # grout
+ add_grout = BoolProperty(
+ name='Add Grout',
+ description='Add grout',
+ default=False,
+ update=update
+ )
+ mortar_depth = FloatProperty(
+ name='Mortar 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='Offset Variance',
+ description='How much to vary the offset each row of tiles',
+ min=0.001, max=100, default=50,
+ precision=2,
+ update=update
+ )
-def update(self, context):
- self.update(context)
+ # UV stuff
+ random_uvs = BoolProperty(
+ name='Random UV\'s', update=update, default=True, description='Random UV positions for the faces'
+ )
+ # bevel
+ bevel = BoolProperty(
+ name='Bevel', update=update, default=False, description='Bevel upper faces'
+ )
+ bevel_amount = FloatProperty(
+ name='Bevel Amount',
+ description='Bevel amount',
+ unit='LENGTH', subtype='DISTANCE',
+ min=0.0001, default=0.001,
+ precision=2, step=0.05,
+ update=update
+ )
-class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
- tile_types = EnumProperty(
- items=(
- ("1", "Tiles", ""),
- ("2", "Large + Small", ""),
- ("3", "Large + Many Small", ""),
- ("4", "Hexagonal", ""),
- ("21", "Planks", ""),
- ("22", "Parquet", ""),
- ("23", "Herringbone Parquet", ""),
- ("24", "Herringbone", "")
- ),
- default="1",
- description="Tile Type",
- update=update,
- name="")
- b_length_s = FloatProperty(
- name="Board Length",
- min=0.01,
- default=2.0,
- unit='LENGTH', subtype='DISTANCE',
- description="Board Length",
- update=update)
- hb_direction = EnumProperty(
- items=(
- ("1", "Forwards (+y)", ""),
- ("2", "Backwards (-y)", ""),
- ("3", "Right (+x)", ""),
- ("4", "Left (-x)", "")
- ),
- name="Direction",
- description="Herringbone Direction",
- update=update)
- thickness = FloatProperty(
- name="Floor Thickness",
- min=0.01,
- default=0.1,
- unit='LENGTH', subtype='DISTANCE',
- description="Thickness Of Flooring",
- update=update)
- num_boards = IntProperty(
- name="# Of Boards",
- min=2,
- max=6,
- default=4,
- description="Number Of Boards In Square",
- update=update)
- space_l = FloatProperty(
- name="Length Spacing",
- min=0.001,
- default=0.005,
- step=0.01,
- unit='LENGTH', subtype='DISTANCE',
- description="Space Between Boards Length Ways",
- update=update)
- space_w = FloatProperty(
- name="Width Spacing",
- min=0.001,
- default=0.005,
- step=0.01,
- unit='LENGTH', subtype='DISTANCE',
- description="Space Between Boards Width Ways",
- update=update)
- spacing = FloatProperty(
- name="Spacing",
- min=0.001,
- default=0.005,
- step=0.01,
- unit='LENGTH', subtype='DISTANCE',
- description="Space Between Tiles/Boards",
- update=update)
- is_bevel = BoolProperty(
- name="Bevel?",
- default=False,
- update=update)
- bevel_res = IntProperty(
- name="Bevel Resolution",
- min=1,
- max=10,
- default=1,
- update=update)
- bevel_amo = FloatProperty(
- name="Bevel Amount",
- min=0.001,
- default=0.0015,
- step=0.01,
- unit='LENGTH', subtype='DISTANCE',
- description="Bevel Amount",
- update=update)
- is_ran_thickness = BoolProperty(
- name="Random Thickness?",
- default=False,
- update=update)
- ran_thickness = FloatProperty(
- name="Thickness Variance",
- min=0.1,
- max=100.0,
- default=50.0,
- subtype="PERCENTAGE",
- update=update)
- is_floor_bottom = BoolProperty(
- name="Floor bottom",
- default=True,
- update=update)
- t_width = FloatProperty(
- name="Tile Width",
- min=0.01,
- default=0.3,
- unit='LENGTH', subtype='DISTANCE',
- description="Tile Width",
- update=update)
- t_length = FloatProperty(
- name="Tile Length",
- min=0.01,
- default=0.3,
- unit='LENGTH', subtype='DISTANCE',
- description="Tile Length",
- update=update)
- is_grout = BoolProperty(
- name="Grout",
- default=False,
- description="Enable grout",
- update=update)
- grout_depth = FloatProperty(
- name="Grout Depth",
- min=0.001,
- default=0.005,
- step=0.01,
- unit='LENGTH', subtype='DISTANCE',
- description="Grout Depth",
- update=update)
- is_offset = BoolProperty(
- name="Offset ?",
- default=False,
- description="Offset Rows",
- update=update)
- offset = FloatProperty(
- name="Offset",
- min=0.001,
- max=100.0,
- default=50.0,
- subtype="PERCENTAGE",
- description="Tile Offset Amount",
- update=update)
- is_random_offset = BoolProperty(
- name="Random Offset?",
- default=False,
- description="Offset Tile Rows Randomly",
- update=update)
- offset_vary = FloatProperty(
- name="Offset Variance",
- min=0.001,
- max=100.0,
- default=50.0,
- subtype="PERCENTAGE",
- description="Offset Variance",
- update=update)
- t_width_s = FloatProperty(
- name="Small Tile Width",
- min=0.02,
- default=0.2,
- unit='LENGTH', subtype='DISTANCE',
- description="Small Tile Width",
- update=update)
- over_width = FloatProperty(
- name="Overall Width",
- min=0.02,
- default=4,
- unit='LENGTH', subtype='DISTANCE',
- description="Overall Width",
- update=update)
- over_length = FloatProperty(
- name="Overall Length",
- min=0.02,
- default=4,
- unit='LENGTH', subtype='DISTANCE',
- description="Overall Length",
- update=update)
- b_width = FloatProperty(
- name="Board Width",
- min=0.01,
- default=0.2,
- unit='LENGTH', subtype='DISTANCE',
- description="Board Width",
- update=update)
- b_length = FloatProperty(
- name="Board Length",
- min=0.01,
- default=0.8,
- unit='LENGTH', subtype='DISTANCE',
- description="Board Length",
- update=update)
- is_length_vary = BoolProperty(
- name="Vary Length?",
- default=False,
- description="Vary Lengths?",
- update=update)
- length_vary = FloatProperty(
- name="Length Variance",
- min=1.00,
- max=100.0,
- default=50.0,
- subtype="PERCENTAGE",
- description="Length Variance",
- update=update)
- max_boards = IntProperty(
- name="Max # Of Boards",
- min=2,
- default=2,
- description="Maximum Number Of Boards Possible In One Length",
- update=update)
- is_width_vary = BoolProperty(
- name="Vary Width?",
- default=False,
- description="Vary Widths?",
- update=update)
- width_vary = FloatProperty(
- name="Width Variance",
- min=1.00,
- max=100.0,
- default=50.0,
- subtype="PERCENTAGE",
- description="Width Variance",
- update=update)
- is_mat_vary = BoolProperty(
+ vary_materials = BoolProperty(
name="Vary Material?",
default=False,
description="Vary Material indexes",
update=update)
- mat_vary = IntProperty(
+ matid = IntProperty(
name="#variations",
min=1,
max=10,
@@ -841,127 +1236,373 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
description="Material index maxi",
update=update)
auto_update = BoolProperty(
- options={'SKIP_SAVE'},
- default=True,
- update=update
+ 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([
+ [1, 0, 0, pt.x],
+ [0, 1, 0, pt.y],
+ [0, 0, 1, pt.z],
+ [0, 0, 0, 1]
+ ])
+ 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)
+ 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:
- # add manipulator for x property
- s = self.manipulators.add()
- s.prop1_name = "over_width"
- # s.prop2_name = "x"
- s.type_key = 'SIZE'
- # add manipulator for y property
+ if len(self.manipulators) < 1:
s = self.manipulators.add()
- s.prop1_name = "over_length"
- # s.prop2_name = "y"
- s.type_key = 'SIZE'
-
- def update(self, context):
+ 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
- self.setup_manipulators()
+ # clean up manipulators before any data model change
+ if manipulable_refresh:
+ self.manipulable_disable(context)
- verts, faces = create_flooring(self.tile_types, self.over_width,
- self.over_length, self.b_width, self.b_length, self.b_length_s,
- self.is_length_vary, self.length_vary, self.num_boards, self.space_l,
- self.space_w, self.spacing, self.t_width, self.t_length, self.is_offset,
- self.offset, self.is_random_offset, self.offset_vary, self.t_width_s,
- self.is_width_vary, self.width_vary, self.max_boards, self.is_ran_thickness,
- self.ran_thickness, self.thickness, self.hb_direction)
-
- if self.is_mat_vary:
- # hexagon made of 2 faces
- if self.tile_types == '4':
- matids = []
- for i in range(int(len(faces) / 2)):
- id = randint(1, self.mat_vary)
- matids.extend([id, id])
- else:
- matids = [randint(1, self.mat_vary) for i in faces]
- else:
- matids = [1 for i in faces]
-
- uvs = [[(0, 0), (0, 1), (1, 1), (1, 0)] for i in faces]
-
- bmed.buildmesh(context,
- o,
- verts,
- faces,
- matids=matids,
- uvs=uvs,
- weld=False,
- auto_smooth=False)
-
- # cut hexa and herringbone wood
- # disable when boolean modifier is found
- enable_bissect = True
- for m in o.modifiers:
- if m.type == 'BOOLEAN':
- enable_bissect = False
-
- if enable_bissect and self.tile_types in ('4', '23', '24'):
- bmed.bissect(context, o, Vector((0, 0, 0)), Vector((0, -1, 0)))
- # Up
- bmed.bissect(context, o, Vector((0, self.over_length, 0)), Vector((0, 1, 0)))
- # left
- bmed.bissect(context, o, Vector((0, 0, 0)), Vector((-1, 0, 0)))
- # right
- bmed.bissect(context, o, Vector((self.over_width, 0, 0)), Vector((1, 0, 0)))
-
- if self.is_bevel:
- bevel = self.bevel_amo
- else:
- bevel = 0
+ g = self.update_parts(o)
- if self.is_grout:
- th = min(self.grout_depth + bevel, self.thickness - 0.001)
- bottom = th
- else:
- th = self.thickness
- bottom = 0
+ g.cut(context, o)
+ g.floor(context, o, self)
- bmed.solidify(context,
- o,
- th,
- floor_bottom=(
- self.is_floor_bottom and
- self.is_ran_thickness and
- self.tile_types in ('21')
- ),
- altitude=bottom)
-
- # bevel mesh
- if self.is_bevel:
- bmed.bevel(context, o, self.bevel_amo, segments=self.bevel_res)
-
- # create grout
- if self.is_grout:
- verts, faces = tile_grout(self.over_width, self.over_length, self.grout_depth, self.thickness)
- matids = [0 for i in faces]
- uvs = [[(0, 0), (0, 1), (1, 1), (1, 0)] for i in faces]
- bmed.addmesh(context,
- o,
- verts,
- faces,
- matids=matids,
- uvs=uvs,
- weld=False,
- auto_smooth=False)
-
- x, y = self.over_width, self.over_length
- self.manipulators[0].set_pts([(0, 0, 0), (x, 0, 0), (1, 0, 0)])
- self.manipulators[1].set_pts([(0, 0, 0), (0, y, 0), (-1, 0, 0)])
+ # 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 witout 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 = [o for o in 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 = True
+ context.scene.objects.active = o.parent
+ d.update(context)
+ o.parent.select = False
+ context.scene.objects.active = o
+
+
+# ------------------------------------------------------------------
+# Define panel class to show object parameters in ui panel (N)
+# ------------------------------------------------------------------
+
class ARCHIPACK_PT_floor(Panel):
bl_idname = "ARCHIPACK_PT_floor"
@@ -980,13 +1621,12 @@ class ARCHIPACK_PT_floor(Panel):
if not archipack_floor.filter(o):
return
layout = self.layout
-
+ scene = context.scene
# retrieve datablock of your object
props = archipack_floor.datablock(o)
-
- # Manipulate mode operator
- layout.operator('archipack.floor_manipulate', icon='HAND')
-
+ # manipulate
+ layout.operator("archipack.floor_manipulate", icon="HAND")
+ layout.separator()
box = layout.box()
row = box.row(align=True)
@@ -1000,124 +1640,170 @@ class ARCHIPACK_PT_floor(Panel):
text="",
icon='ZOOMOUT').remove_active = True
- layout.prop(props, "tile_types", icon="OBJECT_DATA")
+ box = layout.box()
+ box.operator('archipack.floor_cutter').parent = o.name
- layout.separator()
+ 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')
- layout.prop(props, "over_width")
- layout.prop(props, "over_length")
+ box = layout.box()
+ row = box.row()
+ if props.parts_expand:
+ row.prop(props, 'parts_expand', icon="TRIA_DOWN", icon_only=True, 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", icon_only=True, 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()
+ 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")
- # width and lengths
- layout.prop(props, "thickness")
+ # grout
+ box.separator()
+ box.prop(props, 'add_grout', icon='MESH_GRID')
+ if props.add_grout:
+ box.prop(props, 'mortar_depth')
- type = int(props.tile_types)
+ # bevel
+ box.separator()
+ box.prop(props, 'bevel', icon='MOD_BEVEL')
+ if props.bevel:
+ box.prop(props, 'bevel_amount')
- if type > 20:
- # Wood types
- layout.prop(props, "b_width")
- else:
- # Tiles types
- if type != 4:
- # Not hexagonal
- layout.prop(props, "t_width")
- layout.prop(props, "t_length")
- else:
- layout.prop(props, "t_width_s")
-
- # Herringbone
- if type in (23, 24):
- layout.prop(props, "b_length_s")
- layout.prop(props, "hb_direction")
-
- # Parquet
- if type == 22:
- layout.prop(props, "num_boards")
-
- # Planks
- if type == 21:
- layout.prop(props, "b_length")
- layout.prop(props, "space_w")
- layout.prop(props, "space_l")
-
- layout.separator()
- layout.prop(props, "is_length_vary", icon="NLA")
- if props.is_length_vary:
- layout.prop(props, "length_vary")
- layout.prop(props, "max_boards")
-
- layout.separator()
- layout.prop(props, "is_width_vary", icon="UV_ISLANDSEL")
- if props.is_width_vary:
- layout.prop(props, "width_vary")
-
- layout.separator()
- layout.prop(props, "is_ran_thickness", icon="RNDCURVE")
- if props.is_ran_thickness:
- layout.prop(props, "ran_thickness")
- layout.prop(props, "is_floor_bottom", icon="MOVE_DOWN_VEC")
- else:
- layout.prop(props, "spacing")
-
- # Planks and tiles
- if type in (1, 21):
- layout.separator()
- layout.prop(props, "is_offset", icon="OOPS")
- if props.is_offset:
- layout.prop(props, "is_random_offset", icon="NLA")
- if not props.is_random_offset:
- layout.prop(props, "offset")
- else:
- layout.prop(props, "offset_vary")
+ box.separator()
+ box.prop(props, "vary_materials", icon="MATERIAL")
+ if props.vary_materials:
+ box.prop(props, "matid")
- # bevel
- layout.separator()
- layout.prop(props, "is_bevel", icon="MOD_BEVEL")
- if props.is_bevel:
- layout.prop(props, "bevel_res", icon="OUTLINER_DATA_CURVE")
- layout.prop(props, "bevel_amo")
- # Grout
- layout.separator()
- layout.prop(props, "is_grout", icon="OBJECT_DATA")
- if props.is_grout:
- layout.prop(props, "grout_depth")
+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'
- layout.separator()
- layout.prop(props, "is_mat_vary", icon="MATERIAL")
- if props.is_mat_vary:
- layout.prop(props, "mat_vary")
+ @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='HAND')
+ 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 = "Create Floor"
+ bl_description = "Floor"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
def create(self, context):
-
- # Create an empty mesh datablock
+ """
+ expose only basic params in operator
+ use object property for other params
+ """
m = bpy.data.meshes.new("Floor")
-
- # Create an object using the mesh datablock
o = bpy.data.objects.new("Floor", m)
-
- # Add your properties on mesh datablock
d = m.archipack_floor.add()
-
- # Link object into scene
+ # 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
context.scene.objects.link(o)
-
- # select and make active
o.select = True
context.scene.objects.active = o
-
- # Load preset into datablock
self.load_preset(d)
-
- # add a material
self.add_material(o)
return o
@@ -1125,11 +1811,174 @@ class ARCHIPACK_OT_floor(ArchipackCreateTool, Operator):
if context.mode == "OBJECT":
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
- o.location = bpy.context.scene.cursor_location
+ o.location = context.scene.cursor_location
+ # activate manipulators at creation time
o.select = True
context.scene.objects.active = o
+ bpy.ops.archipack.floor_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("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'}
- # Start manipulate mode
+
+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.scene.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 = True
+ context.scene.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)
+ 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
+ context.scene.objects.link(o)
+ o.select = True
+ context.scene.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 = True
+ context.scene.objects.active = o
self.manipulate()
return {'FINISHED'}
else:
@@ -1137,8 +1986,13 @@ class ARCHIPACK_OT_floor(ArchipackCreateTool, Operator):
return {'CANCELLED'}
+# ------------------------------------------------------------------
+# Define operator class to manipulate object
+# ------------------------------------------------------------------
+
+
class ARCHIPACK_OT_floor_preset_menu(PresetMenuOperator, Operator):
- bl_description = "Show Floor Presets"
+ bl_description = "Show Floor presets"
bl_idname = "archipack.floor_preset_menu"
bl_label = "Floor preset"
preset_subdir = "archipack_floor"
@@ -1152,7 +2006,7 @@ class ARCHIPACK_OT_floor_preset(ArchipackPreset, Operator):
@property
def blacklist(self):
- return ['manipulators', 'over_length', 'over_width']
+ return ['manipulators', 'parts', 'n_parts', 'user_defined_path', 'user_defined_resolution']
class ARCHIPACK_OT_floor_manipulate(Operator):
@@ -1171,7 +2025,31 @@ class ARCHIPACK_OT_floor_manipulate(Operator):
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)
@@ -1179,9 +2057,19 @@ def register():
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)
@@ -1189,3 +2077,5 @@ def unregister():
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
index fc1f8c03..c50c61a2 100644
--- a/archipack/archipack_gl.py
+++ b/archipack/archipack_gl.py
@@ -54,116 +54,6 @@ class DefaultColorScheme:
feedback_title_area = (0, 0.4, 0.6, 0.5)
-"""
- # Addon prefs template
-
- feedback_size_main = IntProperty(
- name="Main",
- description="Main title font size (pixels)",
- min=2,
- default=16
- )
- feedback_size_title = IntProperty(
- name="Title",
- description="Tool name font size (pixels)",
- min=2,
- default=14
- )
- feedback_size_shortcut = IntProperty(
- name="Shortcut",
- description="Shortcuts font size (pixels)",
- min=2,
- default=11
- )
- feedback_shortcut_area = FloatVectorProperty(
- name="Background Shortcut",
- description="Shortcut area background color",
- subtype='COLOR_GAMMA',
- default=(0, 0.4, 0.6, 0.2),
- size=4,
- min=0, max=1
- )
- feedback_title_area = FloatVectorProperty(
- name="Background Main",
- description="Title area background color",
- subtype='COLOR_GAMMA',
- default=(0, 0.4, 0.6, 0.5),
- size=4,
- min=0, max=1
- )
- feedback_colour_main = FloatVectorProperty(
- name="Font Main",
- description="Title color",
- subtype='COLOR_GAMMA',
- default=(0.95, 0.95, 0.95, 1.0),
- size=4,
- min=0, max=1
- )
- feedback_colour_key = FloatVectorProperty(
- name="Font Shortcut key",
- description="KEY label color",
- subtype='COLOR_GAMMA',
- default=(0.67, 0.67, 0.67, 1.0),
- size=4,
- min=0, max=1
- )
- feedback_colour_shortcut = FloatVectorProperty(
- name="Font Shortcut hint",
- description="Shortcuts text color",
- subtype='COLOR_GAMMA',
- default=(0.51, 0.51, 0.51, 1.0),
- size=4,
- min=0, max=1
- )
-
- def draw(self, context):
- layout = self.layout
- box = layout.box()
- row = box.row()
- split = row.split(percentage=0.5)
- col = split.column()
- col.label(text="Colors:")
- row = col.row(align=True)
- row.prop(self, "feedback_title_area")
- row = col.row(align=True)
- row.prop(self, "feedback_shortcut_area")
- row = col.row(align=True)
- row.prop(self, "feedback_colour_main")
- row = col.row(align=True)
- row.prop(self, "feedback_colour_key")
- row = col.row(align=True)
- row.prop(self, "feedback_colour_shortcut")
- col = split.column()
- col.label(text="Font size:")
- col.prop(self, "feedback_size_main")
- col.prop(self, "feedback_size_title")
- col.prop(self, "feedback_size_shortcut")
-"""
-
-
-# @TODO:
-# 1 Make a clear separation of 2d (pixel position) and 3d (world position)
-# modes way to set gl coords
-# 2 Unify methods to set points - currently set_pts, set_pos ...
-# 3 Put all Gl part in a sub module as it may be used by other devs
-# as gl toolkit abstraction for screen feedback
-# 4 Implement cursor badges (np_station sample)
-# 5 Define a clear color scheme so it is easy to customize
-# 6 Allow different arguments for each classes like
-# eg: for line p0 p1, p0 and vector (p1-p0)
-# raising exceptions when incomplete
-# 7 Use correct words, normal is not realy a normal
-# but a perpendicular
-# May be hard code more shapes ?
-# Fine tuned text styles with shadows and surronding boxes / backgrounds
-# Extending tests to hdr screens, ultra wide ones and so on
-# Circular handle, handle styling (only border, filling ...)
-
-# Keep point 3 in mind while doing this, to keep it simple and easy to use
-# Take inspiration from other's feed back systems, talk to other devs
-# and find who actually work on bgl future for 2.8 release
-
-
class Gl():
"""
handle 3d -> 2d gl drawing
@@ -205,11 +95,14 @@ class Gl():
return [round(co_2d.x * render_size[0]), round(co_2d.y * render_size[1])]
def _end(self):
+
+ # print("_end")
bgl.glEnd()
bgl.glPopAttrib()
bgl.glLineWidth(1)
bgl.glDisable(bgl.GL_BLEND)
bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
+ # print("_end %s" % (type(self).__name__))
class GlText(Gl):
@@ -347,6 +240,8 @@ class GlText(Gl):
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
x, y = self.position_2d_from_coord(context, self.pts[0], render)
# dirty fast assignment
@@ -381,6 +276,8 @@ class GlBaseLine(Gl):
"""
render flag when rendering
"""
+
+ # print("draw_line %s" % (type(self).__name__))
bgl.glPushAttrib(bgl.GL_ENABLE_BIT)
if self.style == bgl.GL_LINE_STIPPLE:
bgl.glLineStipple(1, 0x9999)
@@ -671,6 +568,8 @@ class GlPolygon(Gl):
"""
render flag when rendering
"""
+
+ # print("draw_polygon")
self.render = render
bgl.glPushAttrib(bgl.GL_ENABLE_BIT)
bgl.glEnable(bgl.GL_BLEND)
@@ -693,6 +592,7 @@ class GlRect(GlPolygon):
GlPolygon.__init__(self, colour, d)
def draw(self, context, render=False):
+
self.render = render
bgl.glPushAttrib(bgl.GL_ENABLE_BIT)
bgl.glEnable(bgl.GL_BLEND)
@@ -1147,7 +1047,7 @@ class GlCursorFence():
"""
Cursor crossing Fence
"""
- def __init__(self, width=1, colour=(1.0, 1.0, 1.0, 0.5), style=2852):
+ def __init__(self, width=1, colour=(1.0, 1.0, 1.0, 0.5), style=bgl.GL_LINE_STIPPLE):
self.line_x = GlLine(d=2)
self.line_x.style = style
self.line_x.width = width
@@ -1184,7 +1084,7 @@ class GlCursorArea():
width=1,
bordercolour=(1.0, 1.0, 1.0, 0.5),
areacolour=(0.5, 0.5, 0.5, 0.08),
- style=2852):
+ style=bgl.GL_LINE_STIPPLE):
self.border = GlPolyline(bordercolour, d=2)
self.border.style = style
@@ -1224,5 +1124,6 @@ class GlCursorArea():
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
index 852fe2b6..fcdb570e 100644
--- a/archipack/archipack_handle.py
+++ b/archipack/archipack_handle.py
@@ -29,6 +29,7 @@ import bpy
def create_handle(context, parent, mesh):
+ old = context.active_object
handle = bpy.data.objects.new("Handle", mesh)
handle['archipack_handle'] = True
context.scene.objects.link(handle)
@@ -37,6 +38,11 @@ def create_handle(context, parent, mesh):
modif.levels = 1
handle.parent = parent
handle.matrix_world = parent.matrix_world.copy()
+ context.scene.objects.active = handle
+ m = handle.archipack_material.add()
+ m.category = 'handle'
+ m.material = 'DEFAULT'
+ context.scene.objects.active = old
return handle
diff --git a/archipack/archipack_manipulator.py b/archipack/archipack_manipulator.py
index c3e0fc24..c96af62a 100644
--- a/archipack/archipack_manipulator.py
+++ b/archipack/archipack_manipulator.py
@@ -776,6 +776,7 @@ class WallSnapManipulator(Manipulator):
part.a0 = w.straight(1, 0).angle
# move object when point 0
self.o.location += sp.delta
+ self.o.matrix_world.translation += sp.delta
if "C_" in part.type:
part.radius = w.r
@@ -1636,6 +1637,11 @@ class DumbAngleManipulator(AngleManipulator):
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
diff --git a/archipack/archipack_material.py b/archipack/archipack_material.py
new file mode 100644
index 00000000..7cb44180
--- /dev/null
+++ b/archipack/archipack_material.py
@@ -0,0 +1,575 @@
+# -*- 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
+ self.path = os.path.join(matlib_path, name)
+ 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()
+
+ 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))
+
+ def has(self, name):
+ return name in self.materials
+
+ def load_mat(self, name, link):
+ """
+ Load a material from library
+ """
+ # print("MatLib.load_mat(%s) linked:%s" % (name, link))
+ with bpy.data.libraries.load(self.path, link, False) as (data_from, data_to):
+ data_to.materials = [name]
+
+ def get_mat(self, name, link):
+ """
+ apply a material by name to active_object
+ into slot index
+ lazy load material list on demand
+ """
+
+ # 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):
+ global __name__
+ prefs = None
+ try:
+ # retrieve addon name from imports
+ addon_name = __name__.split('.')[0]
+ prefs = context.user_preferences.addons[addon_name].preferences
+ except:
+ pass
+ 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 dosent 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("unable to load %s" % mat_path)
+ pass
+
+ def apply(self, context, slot_index, name, link=False):
+
+ o = context.active_object
+ o.select = 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 = {}
+
+ def get_filename(self, object_type):
+
+ target_path = os.path.join("presets", "archipack_materials")
+ target_path = bpy.utils.user_resource('SCRIPTS',
+ target_path,
+ create=True)
+ return os.path.join(target_path, object_type) + '.txt'
+
+ def cleanup(self):
+ self.objects.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
+ if os.path.exists(filename):
+
+ f = open(filename, 'r')
+ 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())
+
+ f.close()
+
+ for s_key in material_sets.keys():
+ self.register_set(object_type, s_key, material_sets[s_key])
+
+ 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 = []
+ for s_key in o_dict.keys():
+ for mat in o_dict[s_key]:
+ lines.append("{}##|##{}\n".format(s_key, mat))
+ f = open(filename, 'w')
+ f.writelines(lines)
+ f.close()
+
+ 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
+ if set_name in self.objects[object_type].keys():
+ self.objects[object_type].pop(set_name)
+ self.save(object_type)
+
+ 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():
+ return None
+ if set_name not in self.objects[object_type].keys():
+ print("set {} not found".format(set_name))
+ return None
+ return self.objects[object_type][set_name]
+
+ def make_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] = {}
+
+ s_keys = self.objects[object_type].keys()
+
+ if len(s_keys) < 1:
+ return [('DEFAULT', 'Default', '', 0)]
+
+ return [(s.upper(), s.capitalize(), '', i) for i, s in enumerate(s_keys)]
+
+
+def material_enum(self, context):
+ global setman
+ if setman is None:
+ setman = MaterialSetManager()
+ return setman.make_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 type",
+ 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:
+ return False
+
+ for ob in sel:
+ context.scene.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.scene.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="ZOOMIN", text="")
+ row.operator('archipack.material_remove', icon="ZOOMOUT", 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 type",
+ 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 res:
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, 'Material {} for {} not found'.format(self.material, self.category))
+ return {'CANCELLED'}
+
+
+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 type",
+ 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)
+ bpy.utils.unregister_class(archipack_material)
+ del Object.archipack_material
diff --git a/archipack/archipack_object.py b/archipack/archipack_object.py
index 18ae43e5..b99fc33b 100644
--- a/archipack/archipack_object.py
+++ b/archipack/archipack_object.py
@@ -36,7 +36,7 @@ from bpy_extras.view3d_utils import (
region_2d_to_origin_3d,
region_2d_to_vector_3d
)
-from .materialutils import MaterialUtils
+# from .materialutils import MaterialUtils
class ArchipackObject():
@@ -117,9 +117,9 @@ class ArchipackObject():
o.select = True
except:
pass
-
- self.previously_active.select = True
- context.scene.objects.active = self.previously_active
+ if self.previously_active is not None:
+ self.previously_active.select = True
+ context.scene.objects.active = self.previously_active
self.previously_selected = None
self.previously_active = None
@@ -156,19 +156,20 @@ class ArchipackCreateTool():
d.auto_update = False
if self.filepath != "":
try:
- # print("Archipack loading preset: %s" % d.auto_update)
bpy.ops.script.python_file_run(filepath=self.filepath)
- # print("Archipack preset loaded auto_update: %s" % d.auto_update)
except:
print("Archipack unable to load preset file : %s" % (self.filepath))
pass
d.auto_update = True
- def add_material(self, o):
+ def add_material(self, o, material='DEFAULT', category=None):
try:
- getattr(MaterialUtils, "add_" + self.archipack_category + "_materials")(o)
+ 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 MaterialUtils.add_%s_materials not found" % (self.archipack_category))
+ print("Archipack %s materials not found" % (self.archipack_category))
pass
def manipulate(self):
diff --git a/archipack/archipack_preset.py b/archipack/archipack_preset.py
index c5fe9446..3092e494 100644
--- a/archipack/archipack_preset.py
+++ b/archipack/archipack_preset.py
@@ -501,11 +501,17 @@ class ArchipackPreset(AddPresetBase):
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):
- return ["d = bpy.context.active_object.data." + self.preset_subdir + "[0]"]
+ 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
diff --git a/archipack/archipack_progressbar.py b/archipack/archipack_progressbar.py
new file mode 100644
index 00000000..16740c99
--- /dev/null
+++ b/archipack/archipack_progressbar.py
@@ -0,0 +1,87 @@
+# -*- 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
+ 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
index d81a6839..5e8f1f59 100644
--- a/archipack/archipack_reference_point.py
+++ b/archipack/archipack_reference_point.py
@@ -29,7 +29,8 @@ from bpy.types import Operator, PropertyGroup, Object, Panel
from bpy.props import (
FloatVectorProperty,
CollectionProperty,
- FloatProperty
+ FloatProperty,
+ EnumProperty
)
from mathutils import Vector
from .bmesh_utils import BmeshEdit as bmed
@@ -55,6 +56,13 @@ class archipack_reference_point(PropertyGroup):
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):
@@ -98,29 +106,61 @@ class archipack_reference_point(PropertyGroup):
return
s = self.symbol_scale
- verts = [(s * x, s * y, s * z) for x, y, z in [
- (-0.25, 0.25, 0.0), (0.25, 0.25, 0.0), (-0.25, -0.25, 0.0), (0.25, -0.25, 0.0),
- (0.0, 0.0, 0.487), (-0.107, 0.107, 0.216), (0.108, 0.107, 0.216), (-0.107, -0.107, 0.216),
- (0.108, -0.107, 0.216), (-0.05, 0.05, 0.5), (0.05, 0.05, 0.5), (0.05, -0.05, 0.5),
- (-0.05, -0.05, 0.5), (-0.193, 0.193, 0.0), (0.193, 0.193, 0.0), (0.193, -0.193, 0.0),
- (-0.193, -0.193, 0.0), (0.0, 0.0, 0.8), (0.0, 0.8, -0.0), (0.0, 0.0, -0.0),
- (0.0, 0.0, 0.0), (0.05, 0.05, 0.674), (-0.05, 0.674, -0.05), (0.0, 0.8, -0.0),
- (-0.05, -0.05, 0.674), (-0.05, 0.674, 0.05), (0.05, 0.674, -0.05), (-0.129, 0.129, 0.162),
- (0.129, 0.129, 0.162), (-0.129, -0.129, 0.162), (0.129, -0.129, 0.162), (0.0, 0.0, 0.8),
- (-0.05, 0.05, 0.674), (0.05, -0.05, 0.674), (0.05, 0.674, 0.05), (0.8, -0.0, -0.0),
- (0.0, -0.0, -0.0), (0.674, 0.05, -0.05), (0.8, -0.0, -0.0), (0.674, 0.05, 0.05),
- (0.674, -0.05, -0.05), (0.674, -0.05, 0.05)]]
-
- edges = [(1, 0), (0, 9), (9, 10), (10, 1), (3, 1), (10, 11),
- (11, 3), (2, 3), (11, 12), (12, 2), (0, 2), (12, 9),
- (6, 5), (8, 6), (7, 8), (5, 7), (17, 24), (17, 20),
- (18, 25), (18, 19), (13, 14), (14, 15), (15, 16), (16, 13),
- (4, 6), (15, 30), (17, 21), (26, 22), (23, 22), (23, 34),
- (18, 26), (28, 27), (30, 28), (29, 30), (27, 29), (14, 28),
- (13, 27), (16, 29), (4, 7), (4, 8), (4, 5), (31, 33),
- (31, 32), (21, 32), (24, 32), (24, 33), (21, 33), (25, 22),
- (25, 34), (26, 34), (35, 39), (35, 36), (40, 37), (38, 37),
- (38, 41), (35, 40), (39, 37), (39, 41), (40, 41)]
+
+ 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()
@@ -154,8 +194,9 @@ class ARCHIPACK_PT_reference_point(Panel):
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(Operator):
@@ -170,6 +211,13 @@ class ARCHIPACK_OT_reference_point(Operator):
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):
@@ -190,6 +238,7 @@ class ARCHIPACK_OT_reference_point(Operator):
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 = True
context.scene.objects.active = o
d.update(context)
@@ -230,6 +279,67 @@ class ARCHIPACK_OT_move_to_3d(Operator):
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, apply_as='DATA',
+ modifier=ctx['modifier'].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 = True
+ context.scene.objects.active = r
+ bpy.ops.object.delete(use_global=False)
+
+ o.select = True
+ context.scene.objects.active = o
+
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
+ return {'CANCELLED'}
+
+
class ARCHIPACK_OT_move_to_2d(Operator):
bl_idname = "archipack.move_to_2d"
bl_label = "Move to 2d"
@@ -354,6 +464,7 @@ def register():
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():
@@ -366,3 +477,4 @@ def unregister():
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_roof.py b/archipack/archipack_roof.py
new file mode 100644
index 00000000..43a63228
--- /dev/null
+++ b/archipack/archipack_roof.py
@@ -0,0 +1,5376 @@
+# -*- 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 neighboors 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 dependancy 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:
+ # contigous, 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
+ # contigous -> 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: rigth one r 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 contigous 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 wich 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 contigous 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 contigous 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 contigous -> sides are same
+ # s2 is root contigous -> sides are same
+ # back to back -> sides are not same
+
+ if s0.reversed:
+ # contigous 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:
+ # contigous 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.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 contigous 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=1)
+
+ 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
+
+ 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 = 3
+
+ 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
+
+ ttl = len(self.pans)
+ step = 100 / ttl
+
+ 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))
+ 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])
+ id = randint(idmat, idmat + rand)
+ t_mats = [id 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=5)
+
+ 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=1)
+
+ 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')
+
+ context.scene.archipack_progress = -1
+
+ def _rake(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 rake(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._rake(s,
+ i,
+ hole, pan,
+ d.rake_width,
+ d.rake_height,
+ d.rake_altitude,
+ d.rake_offset,
+ idmat,
+ verts,
+ faces,
+ edges,
+ matids,
+ uvs)
+
+ for i, s in enumerate(pan.segs):
+ if s.type == 'SIDE':
+ self._rake(s,
+ i,
+ pan, pan,
+ d.rake_width,
+ d.rake_height,
+ d.rake_altitude,
+ d.rake_offset,
+ idmat,
+ verts,
+ faces,
+ edges,
+ matids,
+ uvs)
+
+ def _facia(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 neighboor depending on type
+ if s2.type == 'AXIS' or 'LINK' in s2.type:
+ # apply only on boundarys
+ 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 neighboor 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 facia(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._facia(s,
+ i,
+ hole, pan,
+ False, False,
+ d.facia_width,
+ d.facia_height,
+ d.facia_altitude,
+ d.facia_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._facia(s,
+ i,
+ pan, pan,
+ tri_0, tri_1,
+ d.facia_width,
+ d.facia_height,
+ d.facia_altitude,
+ d.facia_offset,
+ idmat,
+ verts,
+ faces,
+ edges,
+ matids,
+ uvs)
+
+ continue
+
+ f = len(verts)
+ s0 = s.offset(d.facia_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 neighboor depending on type
+ if s1.type == 'AXIS' or 'LINK' in s1.type:
+ # apply only on boundarys
+ 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.facia_width)
+
+ # find next neighboor 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.facia_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.facia_altitude + pan.altitude(s.p0)
+ z1 = self.z + d.facia_altitude + pan.altitude(s.p1)
+ verts.extend([
+ (x0, y0, z0),
+ (x1, y1, z0),
+ (x2, y2, z1),
+ (x3, y3, z1),
+ ])
+ z0 -= d.facia_height
+ z1 -= d.facia_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 neighboor depending on type
+ if s1.type == 'AXIS' or 'LINK' in s1.type:
+ # apply only on boundarys
+ 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 neighboor 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.facia_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.facia_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.facia_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.facia_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':
+ 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':
+ 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
+ z1 = z0 - d.beam_height
+ z2 = self.z + d.beam_alt
+ 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=1)
+
+ 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)
+
+ res, p0, t0 = s0.intersect(s2)
+ res, p1, t1 = s0.intersect(s3)
+
+ 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)
+ res, p0, t = s0.intersect(s1)
+ res, p1, t = s0.intersect(s2)
+ 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':
+ tmin = 0 - d.tile_side / s.length
+ s1 = pan.next_seg(i)
+
+ if s1.type == 'SIDE':
+ 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.scene.objects.active = o.parent
+ bpy.ops.archipack.roof_cutter(parent=d.t_parent)
+ hole_obj = context.active_object
+ else:
+ context.scene.objects.active = hole_obj
+
+ hole_obj.select = 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 = False
+
+ context.scene.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
+ wall.select = True
+ context.scene.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
+ id = 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
+ id = 1
+ else:
+ if d - last_d < 0.001:
+ wd.parts[widx].n_splits -= 1
+ continue
+ wd.parts[widx].z[id] = z
+ wd.parts[widx].t[id] = t - t0
+ t0 = t
+ id += 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 = old_sel
+ context.scene.objects.active = old
+
+ def boundary(self, context, o):
+ """
+ either external or holes cuts
+ """
+ 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 pan in self.pans:
+ pan.slice(g)
+ pan.limits()
+
+ 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=2.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", icon_only=True, 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", icon_only=True, 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(
+ 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="Tri 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 = [o for o in 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("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")
+
+ 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="z",
+ 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,
+ # unit='LENGTH', subtype='DISTANCE',
+ update=update_childs
+ )
+ slope_right = FloatProperty(
+ name="R slope",
+ default=0.5, precision=2, step=1,
+ # unit='LENGTH', subtype='DISTANCE',
+ 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=True
+ )
+ force_update = BoolProperty(
+ options={'SKIP_SAVE'},
+ name="Throttle",
+ default=True
+ )
+
+ 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="x",
+ 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="y",
+ 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="z",
+ 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="x",
+ 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="y",
+ 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="Primary",
+ 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="l",
+ description="Length of hip",
+ min=0.01,
+ default=0.4,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ hip_size_y = FloatProperty(
+ name="w",
+ description="Width of hip",
+ min=0.01,
+ default=0.15,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ hip_size_z = FloatProperty(
+ name="h",
+ 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
+ )
+
+ facia_enable = BoolProperty(
+ name="Enable",
+ description="Enable Facia",
+ default=True,
+ update=update_components
+ )
+ facia_expand = BoolProperty(
+ options={'SKIP_SAVE'},
+ name="Facia",
+ description="Expand facia panel",
+ default=False
+ )
+ facia_height = FloatProperty(
+ name="Height",
+ description="Height",
+ min=0.01,
+ default=0.3,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ facia_width = FloatProperty(
+ name="Width",
+ description="Width",
+ min=0.01,
+ default=0.02,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ facia_offset = FloatProperty(
+ name="Offset",
+ description="Offset from roof border",
+ default=0,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ facia_altitude = FloatProperty(
+ name="Altitude",
+ description="Facia altitude from roof",
+ default=0.1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+
+ rake_enable = BoolProperty(
+ name="Enable",
+ description="Enable Rake",
+ default=True,
+ update=update_components
+ )
+ rake_expand = BoolProperty(
+ options={'SKIP_SAVE'},
+ name="Rake",
+ description="Expand rake panel",
+ default=False
+ )
+ rake_height = FloatProperty(
+ name="Height",
+ description="Height",
+ min=0.01,
+ default=0.3,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ rake_width = FloatProperty(
+ name="Width",
+ description="Width",
+ min=0.01,
+ default=0.02,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ rake_offset = FloatProperty(
+ name="Offset",
+ description="Offset from roof border",
+ default=0.001,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ rake_altitude = FloatProperty(
+ name="Altitude",
+ description="Facia 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
+ )
+
+ 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)
+ 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 = True
+ context.scene.objects.active = child
+ # regenerate hole
+ d.update(context, update_hole=True, update_parent=False)
+ child.select = False
+ o.select = True
+ context.scene.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 - self.t_dist_y * slope
+
+ # 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.rake_enable:
+ g.rake(self, verts, faces, edges, matids, uvs)
+
+ if self.facia_enable:
+ g.facia(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", icon_only=True, 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", icon_only=True, 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 rake', 0),
+ ('BOTTOM', 'Bottom', 'Bottom with gutter', 1),
+ ('LINK', 'Side link', 'Side witout 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 = [o for o in 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.from_points(pts)
+ self.auto_update = True
+ if update_parent:
+ self.update_parent(context, o)
+
+ def update_parent(self, context, o):
+
+ d = archipack_roof.datablock(o.parent)
+ if d is not None:
+ o.parent.select = True
+ context.scene.objects.active = o.parent
+ d.update(context, update_childs=False, update_hole=False)
+ o.parent.select = False
+ context.scene.objects.active = o
+
+
+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()
+ box.operator('archipack.roof_cutter_manipulate', icon='HAND')
+ 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='HAND')
+
+ 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='ZOOMIN')
+ row.operator("archipack.roof_preset", text="", icon='ZOOMOUT').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="Tiles", icon_only=True, emboss=False)
+ else:
+ row.prop(prop, 'tile_expand', icon="TRIA_RIGHT", text="Tiles", icon_only=True, 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")
+ row = box.row(align=True)
+ row.prop(prop, 'tile_size_x')
+ row.prop(prop, 'tile_size_y')
+ row.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")
+ row = box.row(align=True)
+ row.prop(prop, 'tile_space_x')
+ row.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", icon_only=True, emboss=False)
+ else:
+ row.prop(prop, 'hip_expand', icon="TRIA_RIGHT", text="Hip", icon_only=True, emboss=False)
+ row.prop(prop, 'hip_enable')
+ if prop.hip_expand:
+ box.prop(prop, 'hip_model', text="")
+
+ box.label(text="Hip size")
+ row = box.row(align=True)
+ row.prop(prop, 'hip_size_x')
+ row.prop(prop, 'hip_size_y')
+ row.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", icon_only=True, emboss=False)
+ else:
+ row.prop(prop, 'beam_expand', icon="TRIA_RIGHT", text="Beam", icon_only=True, 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", icon_only=True, emboss=False)
+ else:
+ row.prop(prop, 'gutter_expand', icon="TRIA_RIGHT", text="Gutter", icon_only=True, 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.facia_expand:
+ row.prop(prop, 'facia_expand', icon="TRIA_DOWN", text="Facia", icon_only=True, emboss=False)
+ else:
+ row.prop(prop, 'facia_expand', icon="TRIA_RIGHT", text="Facia", icon_only=True, emboss=False)
+ row.prop(prop, 'facia_enable')
+ if prop.facia_expand:
+ box.prop(prop, 'facia_altitude')
+ box.prop(prop, 'facia_width')
+ box.prop(prop, 'facia_height')
+ box.prop(prop, 'facia_offset')
+
+ box = layout.box()
+ row = box.row(align=True)
+ if prop.rake_expand:
+ row.prop(prop, 'rake_expand', icon="TRIA_DOWN", text="Rake", icon_only=True, emboss=False)
+ else:
+ row.prop(prop, 'rake_expand', icon="TRIA_RIGHT", text="Rake", icon_only=True, emboss=False)
+ row.prop(prop, 'rake_enable')
+ if prop.rake_expand:
+ box.prop(prop, 'rake_altitude')
+ box.prop(prop, 'rake_width')
+ box.prop(prop, 'rake_height')
+ box.prop(prop, 'rake_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
+ context.scene.objects.link(o)
+ o.select = True
+ context.scene.objects.active = o
+ self.add_material(o)
+ 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 = True
+ context.scene.objects.active = o
+ self.manipulate()
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
+ return {'CANCELLED'}
+
+
+class ARCHIPACK_OT_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)
+ 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
+ context.scene.objects.link(o)
+ o.select = True
+ context.scene.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 = True
+ context.scene.objects.active = o
+ self.manipulate()
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
+ return {'CANCELLED'}
+
+
+# ------------------------------------------------------------------
+# Define operator class to create object
+# ------------------------------------------------------------------
+
+
+class ARCHIPACK_OT_roof_from_curve(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("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
+ context.scene.objects.link(o)
+ o.select = True
+ context.scene.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 = True
+ context.scene.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, 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
+
+ # allready a timer running
+ self.stop_timer(context)
+
+ # prevent race conditions when allready 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)
+ # print("delay update of %s" % (self.name))
+ if o is not None:
+ selected = o.select
+ o.select = True
+ context.scene.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 = selected
+ context.scene.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']
+
+
+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
index d29c1678..0326a9f0 100644
--- a/archipack/archipack_slab.py
+++ b/archipack/archipack_slab.py
@@ -40,6 +40,11 @@ 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():
@@ -82,11 +87,14 @@ class CurvedSlab(Slab, Arc):
Slab.__init__(self)
-class SlabGenerator():
+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):
@@ -118,13 +126,6 @@ class SlabGenerator():
seg.set_offset(self.parts[i].offset, last)
last = seg.line
- """
- def close(self, closed):
- # Make last segment implicit closing one
- if closed:
- return
- """
-
def close(self, closed):
# Make last segment implicit closing one
if closed:
@@ -146,9 +147,8 @@ class SlabGenerator():
w.v = dp
if len(self.segs) > 1:
- w.line = w.make_offset(self.parts[-1].offset, self.segs[-2])
+ w.line = w.make_offset(self.parts[-1].offset, self.segs[-2].line)
- w = self.segs[-1]
p1 = self.segs[0].line.p1
self.segs[0].line = self.segs[0].make_offset(self.parts[0].offset, w.line)
self.segs[0].line.p1 = p1
@@ -188,14 +188,14 @@ class SlabGenerator():
manipulators[3].set_pts([p0, p1, (1, 0, 0)])
def get_verts(self, verts):
- for slab in self.segs:
- if "Curved" in type(slab).__name__:
+ for s in self.segs:
+ if "Curved" in type(s).__name__:
for i in range(16):
- x, y = slab.line.lerp(i / 16)
- verts.append((x, y, 0))
+ # x, y = slab.line.lerp(i / 16)
+ verts.append(s.lerp(i / 16).to_3d())
else:
- x, y = slab.line.p0
- verts.append((x, y, 0))
+ # x, y = s.line.p0
+ verts.append(s.p0.to_3d())
"""
for i in range(33):
x, y = slab.line.lerp(i / 32)
@@ -238,6 +238,59 @@ class SlabGenerator():
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()
+ 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=1)
+
+ 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)
@@ -491,8 +544,9 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
)
parts = CollectionProperty(type=archipack_slab_part)
closed = BoolProperty(
- default=False,
+ default=True,
name="Close",
+ options={'SKIP_SAVE'},
update=update_manipulators
)
# UI layout related
@@ -939,6 +993,7 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
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"
@@ -1080,11 +1135,15 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
g = self.update_parts(o, update_childs)
- verts = []
+ # relocate before cutting segs
+ self.relocate_childs(context, o, g)
- g.get_verts(verts)
- if len(verts) > 2:
- self.make_surface(o, verts)
+ o.select = True
+ context.scene.objects.active = o
+
+ g.cut(context, o)
+
+ g.slab(context, o, self)
modif = o.modifiers.get('Slab')
if modif is None:
@@ -1106,8 +1165,6 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
(-1, 0, 0)
], normal=g.segs[0].straight(-1, 0).v.to_3d())
- self.relocate_childs(context, o, g)
-
# enable manipulators rebuild
if manipulable_refresh:
self.manipulable_refresh = True
@@ -1168,6 +1225,70 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
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 witout 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 = [o for o in 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 = True
+ context.scene.objects.active = o.parent
+ d.update(context)
+ o.parent.select = False
+ context.scene.objects.active = o
+
+
class ARCHIPACK_PT_slab(Panel):
"""Archipack Slab"""
bl_idname = "ARCHIPACK_PT_slab"
@@ -1182,13 +1303,14 @@ class ARCHIPACK_PT_slab(Panel):
return archipack_slab.filter(context.active_object)
def draw(self, context):
- prop = archipack_slab.datablock(context.active_object)
+ o = context.active_object
+ prop = archipack_slab.datablock(o)
if prop is None:
return
layout = self.layout
- row = layout.row(align=True)
- # self.set_context_3dview(context, row)
- row.operator('archipack.slab_manipulate', icon='HAND')
+ layout.operator('archipack.slab_manipulate', icon='HAND')
+ box = layout.box()
+ box.operator('archipack.slab_cutter').parent = o.name
box = layout.box()
box.prop(prop, 'z')
box = layout.box()
@@ -1205,6 +1327,39 @@ class ARCHIPACK_PT_slab(Panel):
row.prop(prop, 'parts_expand', icon="TRIA_RIGHT", icon_only=True, 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='HAND')
+ 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
# ------------------------------------------------------------------
@@ -1450,6 +1605,75 @@ class ARCHIPACK_OT_slab_from_wall(Operator):
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)
+ 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
+ context.scene.objects.link(o)
+ o.select = True
+ context.scene.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 = True
+ context.scene.objects.active = o
+ self.manipulate()
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
+ return {'CANCELLED'}
+
+
# ------------------------------------------------------------------
# Define operator class to manipulate object
# ------------------------------------------------------------------
@@ -1471,7 +1695,30 @@ class ARCHIPACK_OT_slab_manipulate(Operator):
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)
@@ -1503,3 +1750,9 @@ def unregister():
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
index 936a07d8..eb3898d3 100644
--- a/archipack/archipack_snap.py
+++ b/archipack/archipack_snap.py
@@ -273,10 +273,10 @@ class ARCHIPACK_OT_snap(ArchipackSnapBase, Operator):
# NOTE: this part only run after transform LEFTMOUSE RELEASE
# or with ESC and RIGHTMOUSE
if event.type not in {'ESC', 'RIGHTMOUSE', 'LEFTMOUSE', 'MOUSEMOVE'}:
- # print("Snap.modal skip unknown event %s %s" % (event.type, event.value))
+ print("Snap.modal skip unknown event %s %s" % (event.type, event.value))
# self.report({'WARNING'}, "ARCHIPACK_OT_snap unknown event")
return{'PASS_THROUGH'}
- if event.type in {'ESC', 'RIGHTMOUSE'}:
+ if event.type in ('ESC', 'RIGHTMOUSE'):
SnapStore.callback(context, event, 'CANCEL', self)
else:
SnapStore.placeloc = SnapStore.helper.location
@@ -290,7 +290,6 @@ class ARCHIPACK_OT_snap(ArchipackSnapBase, Operator):
# print("Snap.invoke event %s %s" % (event.type, event.value))
self.init(context, event)
context.window_manager.modal_handler_add(self)
- # print("SnapStore.transform_orientation%s" % (SnapStore.transform_orientation))
bpy.ops.transform.translate('INVOKE_DEFAULT',
constraint_axis=SnapStore.constraint_axis,
constraint_orientation=SnapStore.transform_orientation,
diff --git a/archipack/archipack_stair.py b/archipack/archipack_stair.py
index 9cdd1449..172e1c38 100644
--- a/archipack/archipack_stair.py
+++ b/archipack/archipack_stair.py
@@ -2820,11 +2820,6 @@ class ARCHIPACK_OT_stair_preset(ArchipackPreset, Operator):
def blacklist(self):
return ['manipulators']
- """
- 'presets', 'n_parts', 'parts', 'width', 'height', 'radius',
- 'total_angle', 'da',
- """
-
def register():
bpy.utils.register_class(archipack_stair_material)
diff --git a/archipack/archipack_wall2.py b/archipack/archipack_wall2.py
index 7c42456f..06dabb0e 100644
--- a/archipack/archipack_wall2.py
+++ b/archipack/archipack_wall2.py
@@ -25,7 +25,10 @@
#
# ----------------------------------------------------------
import bpy
-# import time
+import bmesh
+
+import time
+
from bpy.types import Operator, PropertyGroup, Mesh, Panel
from bpy.props import (
FloatProperty, BoolProperty, IntProperty, StringProperty,
@@ -56,6 +59,13 @@ class Wall():
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]
@@ -88,6 +98,11 @@ class Wall():
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)
@@ -130,6 +145,21 @@ class WallGenerator():
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:
@@ -205,18 +235,7 @@ class WallGenerator():
else:
w.v = dp
- def make_wall(self, step_angle, flip, closed, verts, faces):
-
- # swap manipulators so they always face outside
- side = 1
- if flip:
- side = -1
-
- # Make last segment implicit closing one
-
- nb_segs = len(self.segs) - 1
- if closed:
- nb_segs += 1
+ def locate_manipulators(self, side):
for i, wall in enumerate(self.segs):
@@ -260,6 +279,21 @@ class WallGenerator():
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):
@@ -271,6 +305,8 @@ class WallGenerator():
# 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
@@ -298,6 +334,23 @@ class WallGenerator():
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)
@@ -308,6 +361,41 @@ class WallGenerator():
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)
@@ -566,7 +654,6 @@ class archipack_wall2_part(PropertyGroup):
)
z = FloatVectorProperty(
name="height",
- min=0,
default=[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
@@ -1408,6 +1495,9 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
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 [
@@ -1498,12 +1588,32 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
return True
-
-# Update throttle (smell hack here)
+ 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 = True
+ res, pos, normal, face_index, r, matrix_world = context.scene.ray_cast(
+ p,
+ up)
+ o.hide = 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
-# NO MORE USING THIS PART, kept as it as it may be usefull in some cases
+# 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):
@@ -1515,34 +1625,37 @@ class ARCHIPACK_OT_wall2_throttle_update(Operator):
def modal(self, context, event):
global update_timer_updating
if event.type == 'TIMER' and not update_timer_updating:
- update_timer_updating = True
- o = context.scene.objects.get(self.name)
- # print("delay update of %s" % (self.name))
- if o is not None:
- o.select = True
- context.scene.objects.active = o
- d = o.data.archipack_wall2[0]
- g = d.get_generator()
- # update child location and size
- d.relocate_childs(context, o, g)
- # store gl points
- d.update_childs(context, o, g)
- return self.cancel(context)
+ # cant 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)
+ if o is not None:
+ m = o.modifiers.get("AutoBoolean")
+ if m is not None:
+ o.hide = False
+ # o.draw_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
- context.window_manager.event_timer_remove(update_timer)
- update_timer = context.window_manager.event_timer_add(0.1, context.window)
+ 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(0.1, context.window)
+ update_timer = context.window_manager.event_timer_add(throttle_delay, context.window)
return {'RUNNING_MODAL'}
def cancel(self, context):
@@ -1579,8 +1692,10 @@ class ARCHIPACK_PT_wall2(Panel):
row.prop(prop, "closed")
row = layout.row()
row.prop_search(prop, "t_part", context.scene, "objects", text="T link", icon='OBJECT_DATAMODE')
- row = layout.row()
- row.operator("archipack.wall2_reverse", icon='FILE_REFRESH')
+ 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
@@ -1757,6 +1872,29 @@ class ARCHIPACK_OT_wall2_from_slab(Operator):
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
# ------------------------------------------------------------------
@@ -1786,8 +1924,6 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator):
parent = None
takemat = None
- max_style_draw_tool = False
-
@classmethod
def poll(cls, context):
return True
@@ -1928,22 +2064,17 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator):
# wait for takeloc being visible when button is over horizon
rv3d = context.region_data
viewinv = rv3d.view_matrix.inverted()
+
if (takeloc * viewinv).z < 0 or not rv3d.is_perspective:
# print("STARTING")
- # when user press draw button
snap_point(takeloc=takeloc,
callback=self.sp_init,
- # transform_orientation=context.space_data.transform_orientation,
constraint_axis=(True, True, False),
release_confirm=True)
return {'RUNNING_MODAL'}
elif self.state == 'RUNNING':
# print("RUNNING")
- # when user start drawing
-
- # release confirm = False on blender mode
- # release confirm = True on max mode
self.state = 'CREATE'
snap_point(takeloc=self.takeloc,
draw=self.sp_draw,
@@ -1972,6 +2103,7 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator):
evt_value = 'RELEASE'
if event.value == evt_value:
+
if self.flag_next:
self.flag_next = False
o = self.o
@@ -2028,9 +2160,9 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator):
else:
self.o.select = True
context.scene.objects.active = self.o
- d = archipack_wall2.datablock(self.o)
# remove last segment with blender mode
+ d = archipack_wall2.datablock(self.o)
if not self.max_style_draw_tool:
if not d.closed and d.n_parts > 1:
d.n_parts -= 1
@@ -2201,6 +2333,7 @@ def register():
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():
@@ -2218,3 +2351,4 @@ def unregister():
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
index 6768fe2b..a0400f54 100644
--- a/archipack/archipack_window.py
+++ b/archipack/archipack_window.py
@@ -32,11 +32,10 @@ from bpy.props import (
FloatProperty, IntProperty, BoolProperty, BoolVectorProperty,
CollectionProperty, FloatVectorProperty, EnumProperty, StringProperty
)
-from mathutils import Vector
+from mathutils import Vector, Matrix
from math import tan, sqrt
from .bmesh_utils import BmeshEdit as bmed
from .panel import Panel as WindowPanel
-from .materialutils import MaterialUtils
from .archipack_handle import create_handle, window_handle_vertical_01, window_handle_vertical_02
# from .archipack_door_panel import ARCHIPACK_OT_select_parent
from .archipack_manipulator import Manipulable
@@ -54,6 +53,10 @@ 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
@@ -274,6 +277,10 @@ class archipack_window_panel(ArchipackObject, PropertyGroup):
name="Fixed",
default=False
)
+ enable_glass = BoolProperty(
+ name="Enable glass",
+ default=True
+ )
@property
def window(self):
@@ -304,14 +311,22 @@ class archipack_window_panel(ArchipackObject, PropertyGroup):
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=6,
- side_cap_back=7 # cap index
+ side_cap_front=side_cap_front,
+ side_cap_back=side_cap_back # cap index
)
else:
# profil avec chanfrein et joint et support pour verre
@@ -330,14 +345,22 @@ class archipack_window_panel(ArchipackObject, PropertyGroup):
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=8,
- side_cap_back=9 # cap index
+ side_cap_front=side_cap_front,
+ side_cap_back=side_cap_back # cap index
)
@property
@@ -370,7 +393,7 @@ class archipack_window_panel(ArchipackObject, PropertyGroup):
if handle is None:
m = bpy.data.meshes.new("Handle")
handle = create_handle(context, o, m)
- MaterialUtils.add_handle_materials(handle)
+ # MaterialUtils.add_handle_materials(handle)
if self.handle_model == 1:
verts, faces = window_handle_vertical_01(1)
else:
@@ -391,7 +414,6 @@ class archipack_window_panel(ArchipackObject, PropertyGroup):
if o is None:
return
- # update handle, dosent care of instances as window will do
if self.handle == 'NONE':
self.remove_handle(context, o)
else:
@@ -623,7 +645,13 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
),
default='FLAT', update=update,
)
+ enable_glass = BoolProperty(
+ name="Enable glass",
+ default=True,
+ update=update
+ )
warning = BoolProperty(
+ options={'SKIP_SAVE'},
name="warning",
default=False
)
@@ -669,6 +697,12 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
default=True,
update=update
)
+ portal = BoolProperty(
+ default=False,
+ name="Portal",
+ description="Generate a portal",
+ update=update
+ )
@property
def shape(self):
@@ -957,6 +991,44 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
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 == 'LAMP':
+ 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.lamp_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
+ context.scene.objects.unlink(lamp)
+ bpy.data.objects.remove(lamp)
+ bpy.data.lamps.remove(d)
+
+ context.scene.objects.active = o
+
def setup_manipulators(self):
if len(self.manipulators) == 4:
return
@@ -1037,10 +1109,16 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
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]
@@ -1077,7 +1155,9 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
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]]
@@ -1087,7 +1167,6 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
if handle is not None:
if h is None:
h = create_handle(context, p, handle.data)
- MaterialUtils.add_handle_materials(h)
h.location = handle.location.copy()
elif h is not None:
context.scene.objects.unlink(h)
@@ -1095,12 +1174,17 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
p.location = child.location.copy()
+ # restore context
+ context.scene.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
context.scene.objects.link(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()
@@ -1119,6 +1203,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
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)
@@ -1200,12 +1285,15 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
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 = True
@@ -1227,6 +1315,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
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 choosen profile (fixed or not)
# update linked childs location too
@@ -1321,6 +1410,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
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
@@ -1356,7 +1446,14 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
hole_obj['archipack_hole'] = True
hole_obj.parent = o
hole_obj.matrix_world = o.matrix_world.copy()
- MaterialUtils.add_wall2_materials(hole_obj)
+
+ """
+ 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()
@@ -1413,7 +1510,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
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)
+ # MaterialUtils.add_wall2_materials(o)
o.select = True
context.scene.objects.active = o
return o
@@ -1488,6 +1585,8 @@ class ARCHIPACK_PT_window(Panel):
if prop.display_detail:
box = layout.box()
+ box.prop(prop, 'enable_glass')
+ box = layout.box()
box.label("Frame")
box.prop(prop, 'frame_x')
box.prop(prop, 'frame_y')
@@ -1559,6 +1658,8 @@ class ARCHIPACK_PT_window(Panel):
box.prop(prop, 'hole_inside_mat')
box.prop(prop, 'hole_outside_mat')
+ layout.prop(prop, 'portal', icon="LAMP_AREA")
+
class ARCHIPACK_PT_window_panel(Panel):
bl_idname = "ARCHIPACK_PT_window_panel"
@@ -1638,8 +1739,8 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator):
context.scene.objects.link(o)
o.select = True
context.scene.objects.active = o
- self.load_preset(d)
self.add_material(o)
+ self.load_preset(d)
# select frame
o.select = True
context.scene.objects.active = o
@@ -1650,7 +1751,12 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator):
if archipack_window.filter(o):
bpy.ops.archipack.disable_manipulate()
for child in o.children:
- if 'archipack_hole' in child:
+ if child.type == 'LAMP':
+ d = child.data
+ context.scene.objects.unlink(child)
+ bpy.data.objects.remove(child)
+ bpy.data.lamps.remove(d)
+ elif 'archipack_hole' in child:
context.scene.objects.unlink(child)
bpy.data.objects.remove(child, do_unlink=True)
elif child.data is not None and 'archipack_window_panel' in child.data:
@@ -1672,6 +1778,7 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator):
for linked in context.selected_objects:
if linked != o:
archipack_window.datablock(linked).update(context)
+
bpy.ops.object.select_all(action="DESELECT")
o.select = True
context.scene.objects.active = o
@@ -1877,6 +1984,25 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator):
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
# ------------------------------------------------------------------
@@ -1974,6 +2100,14 @@ class ARCHIPACK_OT_window_panel(Operator):
name="Fixed",
default=False
)
+ material = StringProperty(
+ name="material",
+ default=""
+ )
+ enable_glass = BoolProperty(
+ name="Enable glass",
+ default=True
+ )
def draw(self, context):
layout = self.layout
@@ -1999,9 +2133,13 @@ class ARCHIPACK_OT_window_panel(Operator):
d.handle = self.handle
d.handle_model = self.handle_model
d.handle_altitude = self.handle_altitude
+ d.enable_glass = self.enable_glass
context.scene.objects.link(o)
o.select = True
context.scene.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
@@ -2010,7 +2148,6 @@ class ARCHIPACK_OT_window_panel(Operator):
o.lock_scale[1] = True
o.lock_scale[2] = True
d.update(context)
- MaterialUtils.add_window_materials(o)
return o
def execute(self, context):
@@ -2081,6 +2218,7 @@ def register():
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():
@@ -2097,3 +2235,4 @@ def unregister():
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
index b49f4683..3f402d1d 100644
--- a/archipack/bmesh_utils.py
+++ b/archipack/bmesh_utils.py
@@ -43,6 +43,56 @@ class BmeshEdit():
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
@@ -76,19 +126,35 @@ class BmeshEdit():
bm.verts[i].co = v
@staticmethod
- def buildmesh(context, o, verts, faces, matids=None, uvs=None, weld=False, clean=False, auto_smooth=True):
- bm = BmeshEdit._start(context, o)
- bm.clear()
+ 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)
diff --git a/archipack/icons/roof.png b/archipack/icons/roof.png
new file mode 100644
index 00000000..7af48808
--- /dev/null
+++ b/archipack/icons/roof.png
Binary files differ
diff --git a/archipack/materialutils.py b/archipack/materialutils.py
deleted file mode 100644
index 92497924..00000000
--- a/archipack/materialutils.py
+++ /dev/null
@@ -1,169 +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
-
-
-class MaterialUtils():
-
- @staticmethod
- def build_default_mat(name, color=(1.0, 1.0, 1.0)):
- midx = bpy.data.materials.find(name)
- if midx < 0:
- mat = bpy.data.materials.new(name)
- mat.diffuse_color = color
- else:
- mat = bpy.data.materials[midx]
- return mat
-
- @staticmethod
- def add_wall2_materials(obj):
- int_mat = MaterialUtils.build_default_mat('inside', (0.5, 1.0, 1.0))
- out_mat = MaterialUtils.build_default_mat('outside', (0.5, 1.0, 0.5))
- oth_mat = MaterialUtils.build_default_mat('cuts', (1.0, 0.2, 0.2))
- alt1_mat = MaterialUtils.build_default_mat('wall_alternative1', (1.0, 0.2, 0.2))
- alt2_mat = MaterialUtils.build_default_mat('wall_alternative2', (1.0, 0.2, 0.2))
- alt3_mat = MaterialUtils.build_default_mat('wall_alternative3', (1.0, 0.2, 0.2))
- alt4_mat = MaterialUtils.build_default_mat('wall_alternative4', (1.0, 0.2, 0.2))
- alt5_mat = MaterialUtils.build_default_mat('wall_alternative5', (1.0, 0.2, 0.2))
- obj.data.materials.append(out_mat)
- obj.data.materials.append(int_mat)
- obj.data.materials.append(oth_mat)
- obj.data.materials.append(alt1_mat)
- obj.data.materials.append(alt2_mat)
- obj.data.materials.append(alt3_mat)
- obj.data.materials.append(alt4_mat)
- obj.data.materials.append(alt5_mat)
-
- @staticmethod
- def add_wall_materials(obj):
- int_mat = MaterialUtils.build_default_mat('inside', (0.5, 1.0, 1.0))
- out_mat = MaterialUtils.build_default_mat('outside', (0.5, 1.0, 0.5))
- oth_mat = MaterialUtils.build_default_mat('cuts', (1.0, 0.2, 0.2))
- obj.data.materials.append(out_mat)
- obj.data.materials.append(int_mat)
- obj.data.materials.append(oth_mat)
-
- @staticmethod
- def add_slab_materials(obj):
- out_mat = MaterialUtils.build_default_mat('Slab_bottom', (0.5, 1.0, 1.0))
- int_mat = MaterialUtils.build_default_mat('Slab_top', (1.0, 0.2, 0.2))
- oth_mat = MaterialUtils.build_default_mat('Slab_side', (0.5, 1.0, 0.5))
- obj.data.materials.append(out_mat)
- obj.data.materials.append(int_mat)
- obj.data.materials.append(oth_mat)
-
- @staticmethod
- def add_stair_materials(obj):
- cei_mat = MaterialUtils.build_default_mat('Stair_ceiling', (0.5, 1.0, 1.0))
- whi_mat = MaterialUtils.build_default_mat('Stair_white', (1.0, 1.0, 1.0))
- con_mat = MaterialUtils.build_default_mat('Stair_concrete', (0.5, 0.5, 0.5))
- wood_mat = MaterialUtils.build_default_mat('Stair_wood', (0.28, 0.2, 0.1))
- metal_mat = MaterialUtils.build_default_mat('Stair_metal', (0.4, 0.4, 0.4))
- glass_mat = MaterialUtils.build_default_mat('Stair_glass', (0.2, 0.2, 0.2))
- glass_mat.use_transparency = True
- glass_mat.alpha = 0.5
- glass_mat.game_settings.alpha_blend = 'ADD'
- obj.data.materials.append(cei_mat)
- obj.data.materials.append(whi_mat)
- obj.data.materials.append(con_mat)
- obj.data.materials.append(wood_mat)
- obj.data.materials.append(metal_mat)
- obj.data.materials.append(glass_mat)
-
- @staticmethod
- def add_fence_materials(obj):
- wood_mat = MaterialUtils.build_default_mat('Fence_wood', (0.28, 0.2, 0.1))
- metal_mat = MaterialUtils.build_default_mat('Fence_metal', (0.4, 0.4, 0.4))
- glass_mat = MaterialUtils.build_default_mat('Fence_glass', (0.2, 0.2, 0.2))
- glass_mat.use_transparency = True
- glass_mat.alpha = 0.5
- glass_mat.game_settings.alpha_blend = 'ADD'
- obj.data.materials.append(wood_mat)
- obj.data.materials.append(metal_mat)
- obj.data.materials.append(glass_mat)
-
- @staticmethod
- def add_floor_materials(obj):
- con_mat = MaterialUtils.build_default_mat('Floor_grout', (0.5, 0.5, 0.5))
- alt1_mat = MaterialUtils.build_default_mat('Floor_alt1', (0.5, 1.0, 1.0))
- alt2_mat = MaterialUtils.build_default_mat('Floor_alt2', (1.0, 1.0, 1.0))
- alt3_mat = MaterialUtils.build_default_mat('Floor_alt3', (0.28, 0.2, 0.1))
- alt4_mat = MaterialUtils.build_default_mat('Floor_alt4', (0.5, 1.0, 1.0))
- alt5_mat = MaterialUtils.build_default_mat('Floor_alt5', (1.0, 1.0, 0.5))
- alt6_mat = MaterialUtils.build_default_mat('Floor_alt6', (0.28, 0.5, 0.1))
- alt7_mat = MaterialUtils.build_default_mat('Floor_alt7', (0.5, 1.0, 0.5))
- alt8_mat = MaterialUtils.build_default_mat('Floor_alt8', (1.0, 0.2, 1.0))
- alt9_mat = MaterialUtils.build_default_mat('Floor_alt9', (0.28, 0.2, 0.5))
- alt10_mat = MaterialUtils.build_default_mat('Floor_alt10', (0.5, 0.2, 0.1))
- obj.data.materials.append(con_mat)
- obj.data.materials.append(alt1_mat)
- obj.data.materials.append(alt2_mat)
- obj.data.materials.append(alt3_mat)
- obj.data.materials.append(alt4_mat)
- obj.data.materials.append(alt5_mat)
- obj.data.materials.append(alt6_mat)
- obj.data.materials.append(alt7_mat)
- obj.data.materials.append(alt8_mat)
- obj.data.materials.append(alt9_mat)
- obj.data.materials.append(alt10_mat)
-
- @staticmethod
- def add_handle_materials(obj):
- metal_mat = MaterialUtils.build_default_mat('metal', (0.4, 0.4, 0.4))
- obj.data.materials.append(metal_mat)
-
- @staticmethod
- def add_door_materials(obj):
- int_mat = MaterialUtils.build_default_mat('door_inside', (0.7, 0.2, 0.2))
- out_mat = MaterialUtils.build_default_mat('door_outside', (0.7, 0.2, 0.7))
- glass_mat = MaterialUtils.build_default_mat('glass', (0.2, 0.2, 0.2))
- metal_mat = MaterialUtils.build_default_mat('metal', (0.4, 0.4, 0.4))
- glass_mat.use_transparency = True
- glass_mat.alpha = 0.5
- glass_mat.game_settings.alpha_blend = 'ADD'
- obj.data.materials.append(out_mat)
- obj.data.materials.append(int_mat)
- obj.data.materials.append(glass_mat)
- obj.data.materials.append(metal_mat)
-
- @staticmethod
- def add_window_materials(obj):
- int_mat = MaterialUtils.build_default_mat('window_inside', (0.7, 0.2, 0.2))
- out_mat = MaterialUtils.build_default_mat('window_outside', (0.7, 0.2, 0.7))
- glass_mat = MaterialUtils.build_default_mat('glass', (0.2, 0.2, 0.2))
- metal_mat = MaterialUtils.build_default_mat('metal', (0.4, 0.4, 0.4))
- tablet_mat = MaterialUtils.build_default_mat('tablet', (0.2, 0.2, 0.2))
- blind_mat = MaterialUtils.build_default_mat('blind', (0.2, 0.0, 0.0))
- glass_mat.use_transparency = True
- glass_mat.alpha = 0.5
- glass_mat.game_settings.alpha_blend = 'ADD'
- obj.data.materials.append(out_mat)
- obj.data.materials.append(int_mat)
- obj.data.materials.append(glass_mat)
- obj.data.materials.append(metal_mat)
- obj.data.materials.append(tablet_mat)
- obj.data.materials.append(blind_mat)
diff --git a/archipack/presets/archipack_floor/boards_200x20.png b/archipack/presets/archipack_floor/boards_200x20.png
new file mode 100644
index 00000000..86727dae
--- /dev/null
+++ b/archipack/presets/archipack_floor/boards_200x20.png
Binary files differ
diff --git a/archipack/presets/archipack_floor/boards_200x20.py b/archipack/presets/archipack_floor/boards_200x20.py
new file mode 100644
index 00000000..0ec933a5
--- /dev/null
+++ b/archipack/presets/archipack_floor/boards_200x20.py
@@ -0,0 +1,30 @@
+import bpy
+d = bpy.context.active_object.data.archipack_floor[0]
+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.png b/archipack/presets/archipack_floor/herringbone_50x10.png
index b6e7fe56..f5d5b0ef 100644
--- a/archipack/presets/archipack_floor/herringbone_50x10.png
+++ b/archipack/presets/archipack_floor/herringbone_50x10.png
Binary files differ
diff --git a/archipack/presets/archipack_floor/herringbone_50x10.py b/archipack/presets/archipack_floor/herringbone_50x10.py
index a1f196ef..33acfa23 100644
--- a/archipack/presets/archipack_floor/herringbone_50x10.py
+++ b/archipack/presets/archipack_floor/herringbone_50x10.py
@@ -1,34 +1,30 @@
import bpy
d = bpy.context.active_object.data.archipack_floor[0]
-
-d.space_l = 0.004999999888241291
-d.is_width_vary = False
-d.offset_vary = 47.810237884521484
-d.is_ran_thickness = False
-d.b_length = 2.0
-d.t_length = 0.30000001192092896
-d.space_w = 0.004999999888241291
-d.t_width_s = 0.10000000149011612
-d.b_length_s = 0.5
-d.is_grout = False
-d.tile_types = '24'
+d.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.width_vary = 50.0
-d.spacing = 0.0010000000474974513
-d.is_offset = True
-d.is_bevel = False
-d.is_random_offset = True
-d.bevel_amo = 0.001500000013038516
-d.thickness = 0.019999999552965164
-d.bevel_res = 1
-d.max_boards = 2
-d.b_width = 0.10000000149011612
-d.length_vary = 50.0
-d.ran_thickness = 50.0
-d.is_mat_vary = True
-d.hb_direction = '1'
-d.mat_vary = 3
-d.num_boards = 5
-d.t_width = 0.30000001192092896
-d.grout_depth = 0.0010000003967434168
-d.is_length_vary = False
+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.png b/archipack/presets/archipack_floor/herringbone_p_50x10.png
index 1a2b2370..75c9238a 100644
--- a/archipack/presets/archipack_floor/herringbone_p_50x10.png
+++ b/archipack/presets/archipack_floor/herringbone_p_50x10.png
Binary files differ
diff --git a/archipack/presets/archipack_floor/herringbone_p_50x10.py b/archipack/presets/archipack_floor/herringbone_p_50x10.py
index 088a22e4..03a68ec9 100644
--- a/archipack/presets/archipack_floor/herringbone_p_50x10.py
+++ b/archipack/presets/archipack_floor/herringbone_p_50x10.py
@@ -1,34 +1,30 @@
import bpy
d = bpy.context.active_object.data.archipack_floor[0]
-
-d.space_l = 0.004999999888241291
-d.is_width_vary = False
-d.offset_vary = 47.810237884521484
-d.is_ran_thickness = False
-d.b_length = 2.0
-d.t_length = 0.30000001192092896
-d.space_w = 0.004999999888241291
-d.t_width_s = 0.10000000149011612
-d.b_length_s = 0.5
-d.is_grout = False
-d.tile_types = '23'
+d.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.width_vary = 50.0
-d.spacing = 0.0010000000474974513
-d.is_offset = True
-d.is_bevel = False
-d.is_random_offset = True
-d.bevel_amo = 0.001500000013038516
-d.thickness = 0.019999999552965164
-d.bevel_res = 1
-d.max_boards = 2
-d.b_width = 0.10000000149011612
-d.length_vary = 50.0
-d.ran_thickness = 50.0
-d.is_mat_vary = True
-d.hb_direction = '1'
-d.mat_vary = 3
-d.num_boards = 5
-d.t_width = 0.30000001192092896
-d.grout_depth = 0.0010000003967434168
-d.is_length_vary = False
+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.png b/archipack/presets/archipack_floor/hexagon_10.png
new file mode 100644
index 00000000..f0e39743
--- /dev/null
+++ b/archipack/presets/archipack_floor/hexagon_10.png
Binary files differ
diff --git a/archipack/presets/archipack_floor/hexagon_10.py b/archipack/presets/archipack_floor/hexagon_10.py
new file mode 100644
index 00000000..d8db8bef
--- /dev/null
+++ b/archipack/presets/archipack_floor/hexagon_10.py
@@ -0,0 +1,30 @@
+import bpy
+d = bpy.context.active_object.data.archipack_floor[0]
+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 = False
+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.png b/archipack/presets/archipack_floor/hopscotch_30x30.png
new file mode 100644
index 00000000..f64c890b
--- /dev/null
+++ b/archipack/presets/archipack_floor/hopscotch_30x30.png
Binary files differ
diff --git a/archipack/presets/archipack_floor/hopscotch_30x30.py b/archipack/presets/archipack_floor/hopscotch_30x30.py
new file mode 100644
index 00000000..189c9b48
--- /dev/null
+++ b/archipack/presets/archipack_floor/hopscotch_30x30.py
@@ -0,0 +1,30 @@
+import bpy
+d = bpy.context.active_object.data.archipack_floor[0]
+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 = False
+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.png b/archipack/presets/archipack_floor/parquet_15x3.png
index 2b35d58b..22e10310 100644
--- a/archipack/presets/archipack_floor/parquet_15x3.png
+++ b/archipack/presets/archipack_floor/parquet_15x3.png
Binary files differ
diff --git a/archipack/presets/archipack_floor/parquet_15x3.py b/archipack/presets/archipack_floor/parquet_15x3.py
index 5711c93a..2e62961a 100644
--- a/archipack/presets/archipack_floor/parquet_15x3.py
+++ b/archipack/presets/archipack_floor/parquet_15x3.py
@@ -1,34 +1,30 @@
import bpy
d = bpy.context.active_object.data.archipack_floor[0]
-
-d.bevel_res = 1
-d.b_width = 0.029999999329447746
-d.is_bevel = False
-d.hb_direction = '1'
-d.is_width_vary = False
-d.b_length = 2.0
-d.spacing = 0.0010000000474974513
-d.is_grout = False
-d.num_boards = 5
-d.is_length_vary = False
-d.thickness = 0.019999999552965164
-d.is_ran_thickness = False
-d.is_random_offset = True
-d.offset_vary = 47.810237884521484
-d.is_mat_vary = True
-d.tile_types = '22'
-d.length_vary = 50.0
-d.space_w = 0.004999999888241291
-d.ran_thickness = 50.0
-d.max_boards = 2
-d.t_width_s = 0.10000000149011612
-d.t_width = 0.30000001192092896
-d.t_length = 0.30000001192092896
-d.width_vary = 50.0
-d.mat_vary = 3
-d.grout_depth = 0.0010000003967434168
-d.is_offset = True
-d.space_l = 0.004999999888241291
-d.bevel_amo = 0.001500000013038516
+d.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.b_length_s = 2.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/planks_200x20.png b/archipack/presets/archipack_floor/planks_200x20.png
deleted file mode 100644
index 94a49c57..00000000
--- a/archipack/presets/archipack_floor/planks_200x20.png
+++ /dev/null
Binary files differ
diff --git a/archipack/presets/archipack_floor/planks_200x20.py b/archipack/presets/archipack_floor/planks_200x20.py
deleted file mode 100644
index bbea2e66..00000000
--- a/archipack/presets/archipack_floor/planks_200x20.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-
-d.bevel_res = 1
-d.b_width = 0.2
-d.is_bevel = True
-d.hb_direction = '1'
-d.is_width_vary = False
-d.b_length = 2.0
-d.spacing = 0.002
-d.is_grout = False
-d.num_boards = 4
-d.is_length_vary = False
-d.thickness = 0.02
-d.is_ran_thickness = False
-d.is_random_offset = True
-d.offset_vary = 47.81
-d.is_mat_vary = True
-d.tile_types = '21'
-d.length_vary = 50.0
-d.space_w = 0.002
-d.ran_thickness = 50.0
-d.max_boards = 2
-d.t_width_s = 0.1
-d.t_width = 0.3
-d.t_length = 0.3
-d.width_vary = 50.0
-d.mat_vary = 3
-d.grout_depth = 0.001
-d.is_offset = True
-d.space_l = 0.002
-d.bevel_amo = 0.0015
-d.offset = 50.0
-d.b_length_s = 2.0
diff --git a/archipack/presets/archipack_floor/stepping_stone_30x30.png b/archipack/presets/archipack_floor/stepping_stone_30x30.png
new file mode 100644
index 00000000..862e5c1d
--- /dev/null
+++ b/archipack/presets/archipack_floor/stepping_stone_30x30.png
Binary files differ
diff --git a/archipack/presets/archipack_floor/stepping_stone_30x30.py b/archipack/presets/archipack_floor/stepping_stone_30x30.py
new file mode 100644
index 00000000..db85715d
--- /dev/null
+++ b/archipack/presets/archipack_floor/stepping_stone_30x30.py
@@ -0,0 +1,30 @@
+import bpy
+d = bpy.context.active_object.data.archipack_floor[0]
+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 = False
+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.png b/archipack/presets/archipack_floor/tile_30x60.png
new file mode 100644
index 00000000..a1921b22
--- /dev/null
+++ b/archipack/presets/archipack_floor/tile_30x60.png
Binary files differ
diff --git a/archipack/presets/archipack_floor/tile_30x60.py b/archipack/presets/archipack_floor/tile_30x60.py
new file mode 100644
index 00000000..af92cd68
--- /dev/null
+++ b/archipack/presets/archipack_floor/tile_30x60.py
@@ -0,0 +1,30 @@
+import bpy
+d = bpy.context.active_object.data.archipack_floor[0]
+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 = False
+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/tiles_15x15.png b/archipack/presets/archipack_floor/tiles_15x15.png
deleted file mode 100644
index 2a3d8633..00000000
--- a/archipack/presets/archipack_floor/tiles_15x15.png
+++ /dev/null
Binary files differ
diff --git a/archipack/presets/archipack_floor/tiles_15x15.py b/archipack/presets/archipack_floor/tiles_15x15.py
deleted file mode 100644
index d3d244f9..00000000
--- a/archipack/presets/archipack_floor/tiles_15x15.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-
-d.b_width = 0.20000000298023224
-d.width_vary = 50.0
-d.t_width_s = 0.20000000298023224
-d.is_grout = True
-d.tile_types = '1'
-d.space_l = 0.004999999888241291
-d.is_length_vary = False
-d.hb_direction = '1'
-d.offset_vary = 50.0
-d.offset = 50.0
-d.spacing = 0.004999999888241291
-d.thickness = 0.10000000149011612
-d.bevel_res = 1
-d.is_offset = False
-d.grout_depth = 0.0010000003967434168
-d.t_width = 0.15000000596046448
-d.is_ran_thickness = False
-d.is_mat_vary = False
-d.is_random_offset = False
-d.space_w = 0.004999999888241291
-d.is_bevel = True
-d.ran_thickness = 50.0
-d.max_boards = 2
-d.t_length = 0.15000000596046448
-d.b_length_s = 2.0
-d.bevel_amo = 0.001500000013038516
-d.is_width_vary = False
-d.num_boards = 4
-d.length_vary = 50.0
-d.b_length = 0.800000011920929
-d.mat_vary = 1
diff --git a/archipack/presets/archipack_floor/tiles_60x30.png b/archipack/presets/archipack_floor/tiles_60x30.png
deleted file mode 100644
index 16cdf0f1..00000000
--- a/archipack/presets/archipack_floor/tiles_60x30.png
+++ /dev/null
Binary files differ
diff --git a/archipack/presets/archipack_floor/tiles_60x30.py b/archipack/presets/archipack_floor/tiles_60x30.py
deleted file mode 100644
index f8b66129..00000000
--- a/archipack/presets/archipack_floor/tiles_60x30.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-
-d.b_width = 0.20000000298023224
-d.width_vary = 50.0
-d.t_width_s = 0.20000000298023224
-d.is_grout = True
-d.tile_types = '1'
-d.space_l = 0.004999999888241291
-d.is_length_vary = False
-d.hb_direction = '1'
-d.offset_vary = 50.0
-d.offset = 50.0
-d.spacing = 0.004999999888241291
-d.thickness = 0.10000000149011612
-d.bevel_res = 1
-d.is_offset = False
-d.grout_depth = 0.0010000003967434168
-d.t_width = 0.30000001192092896
-d.is_ran_thickness = False
-d.is_mat_vary = False
-d.is_random_offset = False
-d.space_w = 0.004999999888241291
-d.is_bevel = True
-d.ran_thickness = 50.0
-d.max_boards = 2
-d.t_length = 0.6000000238418579
-d.b_length_s = 2.0
-d.bevel_amo = 0.001500000013038516
-d.is_width_vary = False
-d.num_boards = 4
-d.length_vary = 50.0
-d.b_length = 0.800000011920929
-d.mat_vary = 1
diff --git a/archipack/presets/archipack_floor/tiles_hex_10x10.png b/archipack/presets/archipack_floor/tiles_hex_10x10.png
deleted file mode 100644
index 4d4c8ecf..00000000
--- a/archipack/presets/archipack_floor/tiles_hex_10x10.png
+++ /dev/null
Binary files differ
diff --git a/archipack/presets/archipack_floor/tiles_hex_10x10.py b/archipack/presets/archipack_floor/tiles_hex_10x10.py
deleted file mode 100644
index 01086dc8..00000000
--- a/archipack/presets/archipack_floor/tiles_hex_10x10.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-
-d.bevel_res = 1
-d.b_width = 0.20000000298023224
-d.is_bevel = True
-d.hb_direction = '1'
-d.is_width_vary = False
-d.b_length = 0.800000011920929
-d.spacing = 0.004999999888241291
-d.is_grout = True
-d.num_boards = 4
-d.is_length_vary = False
-d.thickness = 0.10000000149011612
-d.is_ran_thickness = False
-d.is_random_offset = False
-d.offset_vary = 50.0
-d.is_mat_vary = False
-d.tile_types = '4'
-d.length_vary = 50.0
-d.space_w = 0.004999999888241291
-d.ran_thickness = 50.0
-d.max_boards = 2
-d.t_width_s = 0.10000000149011612
-d.t_width = 0.30000001192092896
-d.t_length = 0.30000001192092896
-d.width_vary = 50.0
-d.mat_vary = 1
-d.grout_depth = 0.0010000003967434168
-d.is_offset = False
-d.space_l = 0.004999999888241291
-d.bevel_amo = 0.001500000013038516
-d.offset = 50.0
-d.b_length_s = 2.0
diff --git a/archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.png b/archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.png
deleted file mode 100644
index 07c6e266..00000000
--- a/archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.png
+++ /dev/null
Binary files differ
diff --git a/archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.py b/archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.py
deleted file mode 100644
index 3ee45a2d..00000000
--- a/archipack/presets/archipack_floor/tiles_l+ms_30x30_15x15.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-
-d.bevel_res = 1
-d.b_width = 0.20000000298023224
-d.is_bevel = True
-d.hb_direction = '1'
-d.is_width_vary = False
-d.b_length = 0.800000011920929
-d.spacing = 0.004999999888241291
-d.is_grout = True
-d.num_boards = 4
-d.is_length_vary = False
-d.thickness = 0.10000000149011612
-d.is_ran_thickness = False
-d.is_random_offset = False
-d.offset_vary = 50.0
-d.is_mat_vary = False
-d.tile_types = '3'
-d.length_vary = 50.0
-d.space_w = 0.004999999888241291
-d.ran_thickness = 50.0
-d.max_boards = 2
-d.t_width_s = 0.20000000298023224
-d.t_width = 0.30000001192092896
-d.t_length = 0.30000001192092896
-d.width_vary = 50.0
-d.mat_vary = 1
-d.grout_depth = 0.0010000003967434168
-d.is_offset = False
-d.space_l = 0.004999999888241291
-d.bevel_amo = 0.001500000013038516
-d.offset = 50.0
-d.b_length_s = 2.0
diff --git a/archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.png b/archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.png
deleted file mode 100644
index 33d28657..00000000
--- a/archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.png
+++ /dev/null
Binary files differ
diff --git a/archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.py b/archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.py
deleted file mode 100644
index 8f4253fe..00000000
--- a/archipack/presets/archipack_floor/tiles_l+s_30x30_15x15.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import bpy
-d = bpy.context.active_object.data.archipack_floor[0]
-
-d.b_width = 0.20000000298023224
-d.width_vary = 50.0
-d.t_width_s = 0.20000000298023224
-d.is_grout = True
-d.tile_types = '2'
-d.space_l = 0.004999999888241291
-d.is_length_vary = False
-d.hb_direction = '1'
-d.offset_vary = 50.0
-d.offset = 50.0
-d.spacing = 0.004999999888241291
-d.thickness = 0.10000000149011612
-d.bevel_res = 1
-d.is_offset = False
-d.grout_depth = 0.0010000003967434168
-d.t_width = 0.30000001192092896
-d.is_ran_thickness = False
-d.is_mat_vary = False
-d.is_random_offset = False
-d.space_w = 0.004999999888241291
-d.is_bevel = True
-d.ran_thickness = 50.0
-d.max_boards = 2
-d.t_length = 0.30000001192092896
-d.b_length_s = 2.0
-d.bevel_amo = 0.001500000013038516
-d.is_width_vary = False
-d.num_boards = 4
-d.length_vary = 50.0
-d.b_length = 0.800000011920929
-d.mat_vary = 1
diff --git a/archipack/presets/archipack_floor/windmill_30x30.png b/archipack/presets/archipack_floor/windmill_30x30.png
new file mode 100644
index 00000000..4fe5cb93
--- /dev/null
+++ b/archipack/presets/archipack_floor/windmill_30x30.png
Binary files differ
diff --git a/archipack/presets/archipack_floor/windmill_30x30.py b/archipack/presets/archipack_floor/windmill_30x30.py
new file mode 100644
index 00000000..8a690a1a
--- /dev/null
+++ b/archipack/presets/archipack_floor/windmill_30x30.py
@@ -0,0 +1,30 @@
+import bpy
+d = bpy.context.active_object.data.archipack_floor[0]
+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 = False
+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
new file mode 100644
index 00000000..18951498
--- /dev/null
+++ b/archipack/presets/archipack_materials/door.txt
@@ -0,0 +1,4 @@
+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
new file mode 100644
index 00000000..00827582
--- /dev/null
+++ b/archipack/presets/archipack_materials/fence.txt
@@ -0,0 +1,4 @@
+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
new file mode 100644
index 00000000..e04180a6
--- /dev/null
+++ b/archipack/presets/archipack_materials/floor.txt
@@ -0,0 +1,11 @@
+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
diff --git a/archipack/presets/archipack_materials/handle.txt b/archipack/presets/archipack_materials/handle.txt
new file mode 100644
index 00000000..458cb1c2
--- /dev/null
+++ b/archipack/presets/archipack_materials/handle.txt
@@ -0,0 +1,2 @@
+DEFAULT##|##Handle_inside
+DEFAULT##|##Handle_outside
diff --git a/archipack/presets/archipack_materials/roof.txt b/archipack/presets/archipack_materials/roof.txt
new file mode 100644
index 00000000..84e6394e
--- /dev/null
+++ b/archipack/presets/archipack_materials/roof.txt
@@ -0,0 +1,12 @@
+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
diff --git a/archipack/presets/archipack_materials/slab.txt b/archipack/presets/archipack_materials/slab.txt
new file mode 100644
index 00000000..8d3490fe
--- /dev/null
+++ b/archipack/presets/archipack_materials/slab.txt
@@ -0,0 +1,3 @@
+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
new file mode 100644
index 00000000..44966d35
--- /dev/null
+++ b/archipack/presets/archipack_materials/stair.txt
@@ -0,0 +1,6 @@
+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
new file mode 100644
index 00000000..00718d4b
--- /dev/null
+++ b/archipack/presets/archipack_materials/truss.txt
@@ -0,0 +1 @@
+DEFAULT##|##Truss_truss
diff --git a/archipack/presets/archipack_materials/wall2.txt b/archipack/presets/archipack_materials/wall2.txt
new file mode 100644
index 00000000..789c285d
--- /dev/null
+++ b/archipack/presets/archipack_materials/wall2.txt
@@ -0,0 +1,8 @@
+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
new file mode 100644
index 00000000..8f5f8575
--- /dev/null
+++ b/archipack/presets/archipack_materials/window.txt
@@ -0,0 +1,6 @@
+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.png b/archipack/presets/archipack_roof/braas_1.png
new file mode 100644
index 00000000..98d831f3
--- /dev/null
+++ b/archipack/presets/archipack_roof/braas_1.png
Binary files differ
diff --git a/archipack/presets/archipack_roof/braas_1.py b/archipack/presets/archipack_roof/braas_1.py
new file mode 100644
index 00000000..5ba9e6c6
--- /dev/null
+++ b/archipack/presets/archipack_roof/braas_1.py
@@ -0,0 +1,30 @@
+import bpy
+d = bpy.context.active_object.data.archipack_roof[0]
+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 \ No newline at end of file
diff --git a/archipack/presets/archipack_roof/braas_2.png b/archipack/presets/archipack_roof/braas_2.png
new file mode 100644
index 00000000..8db07470
--- /dev/null
+++ b/archipack/presets/archipack_roof/braas_2.png
Binary files differ
diff --git a/archipack/presets/archipack_roof/braas_2.py b/archipack/presets/archipack_roof/braas_2.py
new file mode 100644
index 00000000..bd573c8e
--- /dev/null
+++ b/archipack/presets/archipack_roof/braas_2.py
@@ -0,0 +1,30 @@
+import bpy
+d = bpy.context.active_object.data.archipack_roof[0]
+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 \ No newline at end of file
diff --git a/archipack/presets/archipack_roof/eternit.png b/archipack/presets/archipack_roof/eternit.png
new file mode 100644
index 00000000..874901f5
--- /dev/null
+++ b/archipack/presets/archipack_roof/eternit.png
Binary files differ
diff --git a/archipack/presets/archipack_roof/eternit.py b/archipack/presets/archipack_roof/eternit.py
new file mode 100644
index 00000000..033cbf11
--- /dev/null
+++ b/archipack/presets/archipack_roof/eternit.py
@@ -0,0 +1,30 @@
+import bpy
+d = bpy.context.active_object.data.archipack_roof[0]
+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 \ No newline at end of file
diff --git a/archipack/presets/archipack_roof/lauze.png b/archipack/presets/archipack_roof/lauze.png
new file mode 100644
index 00000000..925f46bf
--- /dev/null
+++ b/archipack/presets/archipack_roof/lauze.png
Binary files differ
diff --git a/archipack/presets/archipack_roof/lauze.py b/archipack/presets/archipack_roof/lauze.py
new file mode 100644
index 00000000..987d55bc
--- /dev/null
+++ b/archipack/presets/archipack_roof/lauze.py
@@ -0,0 +1,30 @@
+import bpy
+d = bpy.context.active_object.data.archipack_roof[0]
+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 \ No newline at end of file
diff --git a/archipack/presets/archipack_roof/metal.png b/archipack/presets/archipack_roof/metal.png
new file mode 100644
index 00000000..dbcc7fee
--- /dev/null
+++ b/archipack/presets/archipack_roof/metal.png
Binary files differ
diff --git a/archipack/presets/archipack_roof/metal.py b/archipack/presets/archipack_roof/metal.py
new file mode 100644
index 00000000..33d35f66
--- /dev/null
+++ b/archipack/presets/archipack_roof/metal.py
@@ -0,0 +1,30 @@
+import bpy
+d = bpy.context.active_object.data.archipack_roof[0]
+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.png b/archipack/presets/archipack_roof/ondule.png
new file mode 100644
index 00000000..d3d99ac4
--- /dev/null
+++ b/archipack/presets/archipack_roof/ondule.png
Binary files differ
diff --git a/archipack/presets/archipack_roof/ondule.py b/archipack/presets/archipack_roof/ondule.py
new file mode 100644
index 00000000..68be8fa4
--- /dev/null
+++ b/archipack/presets/archipack_roof/ondule.py
@@ -0,0 +1,29 @@
+import bpy
+d = bpy.context.active_object.data.archipack_roof[0]
+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.png b/archipack/presets/archipack_roof/roman.png
new file mode 100644
index 00000000..20615e27
--- /dev/null
+++ b/archipack/presets/archipack_roof/roman.png
Binary files differ
diff --git a/archipack/presets/archipack_roof/roman.py b/archipack/presets/archipack_roof/roman.py
new file mode 100644
index 00000000..6f3849dd
--- /dev/null
+++ b/archipack/presets/archipack_roof/roman.py
@@ -0,0 +1,29 @@
+import bpy
+d = bpy.context.active_object.data.archipack_roof[0]
+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.png b/archipack/presets/archipack_roof/round.png
new file mode 100644
index 00000000..9bd57982
--- /dev/null
+++ b/archipack/presets/archipack_roof/round.png
Binary files differ
diff --git a/archipack/presets/archipack_roof/round.py b/archipack/presets/archipack_roof/round.py
new file mode 100644
index 00000000..8da9a049
--- /dev/null
+++ b/archipack/presets/archipack_roof/round.py
@@ -0,0 +1,30 @@
+import bpy
+d = bpy.context.active_object.data.archipack_roof[0]
+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 \ No newline at end of file
diff --git a/archipack/presets/archipack_roof/square.png b/archipack/presets/archipack_roof/square.png
new file mode 100644
index 00000000..97b8d966
--- /dev/null
+++ b/archipack/presets/archipack_roof/square.png
Binary files differ
diff --git a/archipack/presets/archipack_roof/square.py b/archipack/presets/archipack_roof/square.py
new file mode 100644
index 00000000..a26a15a8
--- /dev/null
+++ b/archipack/presets/archipack_roof/square.py
@@ -0,0 +1,30 @@
+import bpy
+d = bpy.context.active_object.data.archipack_roof[0]
+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 \ No newline at end of file
diff --git a/archipack/presets/archipack_stair/u_wood_over_concrete.py b/archipack/presets/archipack_stair/u_wood_over_concrete.py
index b523dcde..ba16dde5 100644
--- a/archipack/presets/archipack_stair/u_wood_over_concrete.py
+++ b/archipack/presets/archipack_stair/u_wood_over_concrete.py
@@ -1,6 +1,6 @@
import bpy
d = bpy.context.active_object.data.archipack_stair[0]
-
+d.auto_update = False
d.steps_type = 'CLOSED'
d.handrail_slice_right = True
d.total_angle = 6.2831854820251465