diff options
author | Stephen Leger <stephen@3dservices.ch> | 2017-07-22 14:25:28 +0300 |
---|---|---|
committer | Stephen Leger <stephen@3dservices.ch> | 2017-07-22 14:26:04 +0300 |
commit | c1ab9b4b9c6c0226f8d7789b92efda9b0f33cfd1 (patch) | |
tree | 37d5a97c758fa9af48d1dfb5428edd72072d882a /archipack/archipack_fence.py | |
parent | 5638a8783502138500912061dde0e8ee476d7fca (diff) |
archipack: T52120 release to official
Diffstat (limited to 'archipack/archipack_fence.py')
-rw-r--r-- | archipack/archipack_fence.py | 1782 |
1 files changed, 1782 insertions, 0 deletions
diff --git a/archipack/archipack_fence.py b/archipack/archipack_fence.py new file mode 100644 index 00000000..961b516e --- /dev/null +++ b/archipack/archipack_fence.py @@ -0,0 +1,1782 @@ +# -*- coding:utf-8 -*- + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +# noinspection PyUnresolvedReferences +import bpy +# noinspection PyUnresolvedReferences +from bpy.types import Operator, PropertyGroup, Mesh, Panel +from bpy.props import ( + FloatProperty, BoolProperty, IntProperty, CollectionProperty, + StringProperty, EnumProperty, FloatVectorProperty + ) +from .bmesh_utils import BmeshEdit as bmed +from .panel import Panel as Lofter +from mathutils import Vector, Matrix +from mathutils.geometry import interpolate_bezier +from math import sin, cos, pi, acos, atan2 +from .archipack_manipulator import Manipulable, archipack_manipulator +from .archipack_2d import Line, Arc +from .archipack_preset import ArchipackPreset, PresetMenuOperator +from .archipack_object import ArchipackCreateTool, ArchipackObject + + +class Fence(): + + def __init__(self): + # total distance from start + self.dist = 0 + self.t_start = 0 + self.t_end = 0 + self.dz = 0 + self.z0 = 0 + self.a0 = 0 + + def set_offset(self, offset, last=None): + """ + Offset line and compute intersection point + between segments + """ + self.line = self.make_offset(offset, last) + + @property + def t_diff(self): + return self.t_end - self.t_start + + def straight_fence(self, a0, length): + s = self.straight(length).rotate(a0) + return StraightFence(s.p, s.v) + + def curved_fence(self, a0, da, radius): + n = self.normal(1).rotate(a0).scale(radius) + if da < 0: + n.v = -n.v + a0 = n.angle + c = n.p - n.v + return CurvedFence(c, radius, a0, da) + + +class StraightFence(Fence, Line): + def __str__(self): + return "t_start:{} t_end:{} dist:{}".format(self.t_start, self.t_end, self.dist) + + def __init__(self, p, v): + Fence.__init__(self) + Line.__init__(self, p, v) + + +class CurvedFence(Fence, Arc): + def __str__(self): + return "t_start:{} t_end:{} dist:{}".format(self.t_start, self.t_end, self.dist) + + def __init__(self, c, radius, a0, da): + Fence.__init__(self) + Arc.__init__(self, c, radius, a0, da) + + +class FenceSegment(): + def __str__(self): + return "t_start:{} t_end:{} n_step:{} t_step:{} i_start:{} i_end:{}".format( + self.t_start, self.t_end, self.n_step, self.t_step, self.i_start, self.i_end) + + def __init__(self, t_start, t_end, n_step, t_step, i_start, i_end): + self.t_start = t_start + self.t_end = t_end + self.n_step = n_step + self.t_step = t_step + self.i_start = i_start + self.i_end = i_end + + +class FenceGenerator(): + + def __init__(self, parts): + self.parts = parts + self.segs = [] + self.length = 0 + self.user_defined_post = None + self.user_defined_uvs = None + self.user_defined_mat = None + + def add_part(self, part): + + if len(self.segs) < 1: + s = None + else: + s = self.segs[-1] + + # start a new fence + if s is None: + if part.type == 'S_FENCE': + p = Vector((0, 0)) + v = part.length * Vector((cos(part.a0), sin(part.a0))) + s = StraightFence(p, v) + elif part.type == 'C_FENCE': + c = -part.radius * Vector((cos(part.a0), sin(part.a0))) + s = CurvedFence(c, part.radius, part.a0, part.da) + else: + if part.type == 'S_FENCE': + s = s.straight_fence(part.a0, part.length) + elif part.type == 'C_FENCE': + s = s.curved_fence(part.a0, part.da, part.radius) + + # s.dist = self.length + # self.length += s.length + self.segs.append(s) + self.last_type = type + + def set_offset(self, offset): + # @TODO: + # re-evaluate length of offset line here + last = None + for seg in self.segs: + seg.set_offset(offset, last) + last = seg.line + + def param_t(self, angle_limit, post_spacing): + """ + setup corners and fences dz + compute index of fences wich belong to each group of fences between corners + compute t of each fence + """ + # segments are group of parts separated by limit angle + self.segments = [] + i_start = 0 + t_start = 0 + dist_0 = 0 + z = 0 + self.length = 0 + n_parts = len(self.parts) - 1 + for i, f in enumerate(self.segs): + f.dist = self.length + self.length += f.line.length + + vz0 = Vector((1, 0)) + angle_z = 0 + for i, f in enumerate(self.segs): + dz = self.parts[i].dz + if f.dist > 0: + f.t_start = f.dist / self.length + else: + f.t_start = 0 + + f.t_end = (f.dist + f.line.length) / self.length + f.z0 = z + f.dz = dz + z += dz + + if i < n_parts: + + vz1 = Vector((self.segs[i + 1].length, self.parts[i + 1].dz)) + angle_z = abs(vz0.angle_signed(vz1)) + vz0 = vz1 + + if (abs(self.parts[i + 1].a0) >= angle_limit or angle_z >= angle_limit): + l_seg = f.dist + f.line.length - dist_0 + t_seg = f.t_end - t_start + n_fences = max(1, int(l_seg / post_spacing)) + t_fence = t_seg / n_fences + segment = FenceSegment(t_start, f.t_end, n_fences, t_fence, i_start, i) + dist_0 = f.dist + f.line.length + t_start = f.t_end + i_start = i + self.segments.append(segment) + + manipulators = self.parts[i].manipulators + p0 = f.line.p0.to_3d() + p1 = f.line.p1.to_3d() + # angle from last to current segment + if i > 0: + v0 = self.segs[i - 1].line.straight(-1, 1).v.to_3d() + v1 = f.line.straight(1, 0).v.to_3d() + manipulators[0].set_pts([p0, v0, v1]) + + if type(f).__name__ == "StraightFence": + # segment length + manipulators[1].type_key = 'SIZE' + manipulators[1].prop1_name = "length" + manipulators[1].set_pts([p0, p1, (1, 0, 0)]) + else: + # segment radius + angle + v0 = (f.line.p0 - f.c).to_3d() + v1 = (f.line.p1 - f.c).to_3d() + manipulators[1].type_key = 'ARC_ANGLE_RADIUS' + manipulators[1].prop1_name = "da" + manipulators[1].prop2_name = "radius" + manipulators[1].set_pts([f.c.to_3d(), v0, v1]) + + # snap manipulator, dont change index ! + manipulators[2].set_pts([p0, p1, (1, 0, 0)]) + + f = self.segs[-1] + l_seg = f.dist + f.line.length - dist_0 + t_seg = f.t_end - t_start + n_fences = max(1, int(l_seg / post_spacing)) + t_fence = t_seg / n_fences + segment = FenceSegment(t_start, f.t_end, n_fences, t_fence, i_start, len(self.segs) - 1) + self.segments.append(segment) + + def setup_user_defined_post(self, o, post_x, post_y, post_z): + self.user_defined_post = o + x = o.bound_box[6][0] - o.bound_box[0][0] + y = o.bound_box[6][1] - o.bound_box[0][1] + z = o.bound_box[6][2] - o.bound_box[0][2] + self.user_defined_post_scale = Vector((post_x / x, post_y / -y, post_z / z)) + m = o.data + # create vertex group lookup dictionary for names + vgroup_names = {vgroup.index: vgroup.name for vgroup in o.vertex_groups} + # create dictionary of vertex group assignments per vertex + self.vertex_groups = [[vgroup_names[g.group] for g in v.groups] for v in m.vertices] + # uvs + uv_act = m.uv_layers.active + if uv_act is not None: + uv_layer = uv_act.data + self.user_defined_uvs = [[uv_layer[li].uv for li in p.loop_indices] for p in m.polygons] + else: + self.user_defined_uvs = [[(0, 0) for i in p.vertices] for p in m.polygons] + # material ids + self.user_defined_mat = [p.material_index for p in m.polygons] + + def get_user_defined_post(self, tM, z0, z1, z2, slope, post_z, verts, faces, matids, uvs): + f = len(verts) + m = self.user_defined_post.data + for i, g in enumerate(self.vertex_groups): + co = m.vertices[i].co.copy() + co.x *= self.user_defined_post_scale.x + co.y *= self.user_defined_post_scale.y + co.z *= self.user_defined_post_scale.z + if 'Slope' in g: + co.z += co.y * slope + verts.append(tM * co) + matids += self.user_defined_mat + faces += [tuple([i + f for i in p.vertices]) for p in m.polygons] + uvs += self.user_defined_uvs + + def get_post(self, post, post_x, post_y, post_z, post_alt, sub_offset_x, + id_mat, verts, faces, matids, uvs): + + n, dz, zl = post + slope = dz * post_y + + if self.user_defined_post is not None: + x, y = -n.v.normalized() + p = n.p + sub_offset_x * n.v.normalized() + tM = Matrix([ + [x, y, 0, p.x], + [y, -x, 0, p.y], + [0, 0, 1, zl + post_alt], + [0, 0, 0, 1] + ]) + self.get_user_defined_post(tM, zl, 0, 0, dz, post_z, verts, faces, matids, uvs) + return + + z3 = zl + post_z + post_alt - slope + z4 = zl + post_z + post_alt + slope + z0 = zl + post_alt - slope + z1 = zl + post_alt + slope + vn = n.v.normalized() + dx = post_x * vn + dy = post_y * Vector((vn.y, -vn.x)) + oy = sub_offset_x * vn + x0, y0 = n.p - dx + dy + oy + x1, y1 = n.p - dx - dy + oy + x2, y2 = n.p + dx - dy + oy + x3, y3 = n.p + dx + dy + oy + f = len(verts) + verts.extend([(x0, y0, z0), (x0, y0, z3), + (x1, y1, z1), (x1, y1, z4), + (x2, y2, z1), (x2, y2, z4), + (x3, y3, z0), (x3, y3, z3)]) + faces.extend([(f, f + 1, f + 3, f + 2), + (f + 2, f + 3, f + 5, f + 4), + (f + 4, f + 5, f + 7, f + 6), + (f + 6, f + 7, f + 1, f), + (f, f + 2, f + 4, f + 6), + (f + 7, f + 5, f + 3, f + 1)]) + matids.extend([id_mat, id_mat, id_mat, id_mat, id_mat, id_mat]) + x = [(0, 0), (0, post_z), (post_x, post_z), (post_x, 0)] + y = [(0, 0), (0, post_z), (post_y, post_z), (post_y, 0)] + z = [(0, 0), (post_x, 0), (post_x, post_y), (0, post_y)] + uvs.extend([x, y, x, y, z, z]) + + def get_panel(self, subs, altitude, panel_x, panel_z, sub_offset_x, idmat, verts, faces, matids, uvs): + n_subs = len(subs) + if n_subs < 1: + return + f = len(verts) + x0 = sub_offset_x - 0.5 * panel_x + x1 = sub_offset_x + 0.5 * panel_x + z0 = 0 + z1 = panel_z + profile = [Vector((x0, z0)), Vector((x1, z0)), Vector((x1, z1)), Vector((x0, z1))] + user_path_uv_v = [] + n_sections = n_subs - 1 + n, dz, zl = subs[0] + p0 = n.p + v0 = n.v.normalized() + for s, section in enumerate(subs): + n, dz, zl = section + p1 = n.p + if s < n_sections: + v1 = subs[s + 1][0].v.normalized() + dir = (v0 + v1).normalized() + scale = 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1)))) + for p in profile: + x, y = n.p + scale * p.x * dir + z = zl + p.y + altitude + verts.append((x, y, z)) + if s > 0: + user_path_uv_v.append((p1 - p0).length) + p0 = p1 + v0 = v1 + + # build faces using Panel + lofter = Lofter( + # closed_shape, index, x, y, idmat + True, + [i for i in range(len(profile))], + [p.x for p in profile], + [p.y for p in profile], + [idmat for i in range(len(profile))], + closed_path=False, + user_path_uv_v=user_path_uv_v, + user_path_verts=n_subs + ) + faces += lofter.faces(16, offset=f, path_type='USER_DEFINED') + matids += lofter.mat(16, idmat, idmat, path_type='USER_DEFINED') + v = Vector((0, 0)) + uvs += lofter.uv(16, v, v, v, v, 0, v, 0, 0, path_type='USER_DEFINED') + + def make_subs(self, x, y, z, post_y, altitude, + sub_spacing, offset_x, sub_offset_x, mat, verts, faces, matids, uvs): + + t_post = (0.5 * post_y - y) / self.length + t_spacing = (sub_spacing + y) / self.length + + for segment in self.segments: + t_step = segment.t_step + t_start = segment.t_start + t_post + s = 0 + s_sub = t_step - 2 * t_post + n_sub = int(s_sub / t_spacing) + if n_sub > 0: + t_sub = s_sub / n_sub + else: + t_sub = 1 + i = segment.i_start + while s < segment.n_step: + t_cur = t_start + s * t_step + for j in range(1, n_sub): + t_s = t_cur + t_sub * j + while self.segs[i].t_end < t_s: + i += 1 + f = self.segs[i] + t = (t_s - f.t_start) / f.t_diff + n = f.line.normal(t) + post = (n, f.dz / f.length, f.z0 + f.dz * t) + self.get_post(post, x, y, z, altitude, sub_offset_x, mat, verts, faces, matids, uvs) + s += 1 + + def make_post(self, x, y, z, altitude, x_offset, mat, verts, faces, matids, uvs): + + for segment in self.segments: + t_step = segment.t_step + t_start = segment.t_start + s = 0 + i = segment.i_start + while s < segment.n_step: + t_cur = t_start + s * t_step + while self.segs[i].t_end < t_cur: + i += 1 + f = self.segs[i] + t = (t_cur - f.t_start) / f.t_diff + n = f.line.normal(t) + post = (n, f.dz / f.line.length, f.z0 + f.dz * t) + # self.get_post(post, x, y, z, altitude, x_offset, mat, verts, faces, matids, uvs) + self.get_post(post, x, y, z, altitude, 0, mat, verts, faces, matids, uvs) + s += 1 + + if segment.i_end + 1 == len(self.segs): + f = self.segs[segment.i_end] + n = f.line.normal(1) + post = (n, f.dz / f.line.length, f.z0 + f.dz) + # self.get_post(post, x, y, z, altitude, x_offset, mat, verts, faces, matids, uvs) + self.get_post(post, x, y, z, altitude, 0, mat, verts, faces, matids, uvs) + + def make_panels(self, x, z, post_y, altitude, panel_dist, + offset_x, sub_offset_x, idmat, verts, faces, matids, uvs): + + t_post = (0.5 * post_y + panel_dist) / self.length + for segment in self.segments: + t_step = segment.t_step + t_start = segment.t_start + s = 0 + i = segment.i_start + while s < segment.n_step: + subs = [] + t_cur = t_start + s * t_step + t_post + t_end = t_start + (s + 1) * t_step - t_post + # find first section + while self.segs[i].t_end < t_cur and i < segment.i_end: + i += 1 + f = self.segs[i] + # 1st section + t = (t_cur - f.t_start) / f.t_diff + n = f.line.normal(t) + subs.append((n, f.dz / f.line.length, f.z0 + f.dz * t)) + # crossing sections -> new segment + while i < segment.i_end: + f = self.segs[i] + if f.t_end < t_end: + if type(f).__name__ == 'CurvedFence': + # cant end after segment + t0 = max(0, (t_cur - f.t_start) / f.t_diff) + t1 = min(1, (t_end - f.t_start) / f.t_diff) + n_s = int(max(1, abs(f.da) * (5) / pi - 1)) + dt = (t1 - t0) / n_s + for j in range(1, n_s + 1): + t = t0 + dt * j + n = f.line.sized_normal(t, 1) + # n.p = f.lerp(x_offset) + subs.append((n, f.dz / f.line.length, f.z0 + f.dz * t)) + else: + n = f.line.normal(1) + subs.append((n, f.dz / f.line.length, f.z0 + f.dz)) + if f.t_end >= t_end: + break + elif f.t_start < t_end: + i += 1 + + f = self.segs[i] + # last section + if type(f).__name__ == 'CurvedFence': + # cant start before segment + t0 = max(0, (t_cur - f.t_start) / f.t_diff) + t1 = min(1, (t_end - f.t_start) / f.t_diff) + n_s = int(max(1, abs(f.da) * (5) / pi - 1)) + dt = (t1 - t0) / n_s + for j in range(1, n_s + 1): + t = t0 + dt * j + n = f.line.sized_normal(t, 1) + # n.p = f.lerp(x_offset) + subs.append((n, f.dz / f.line.length, f.z0 + f.dz * t)) + else: + t = (t_end - f.t_start) / f.t_diff + n = f.line.normal(t) + subs.append((n, f.dz / f.line.length, f.z0 + f.dz * t)) + + # self.get_panel(subs, altitude, x, z, 0, idmat, verts, faces, matids, uvs) + self.get_panel(subs, altitude, x, z, sub_offset_x, idmat, verts, faces, matids, uvs) + s += 1 + + def make_profile(self, profile, idmat, + x_offset, z_offset, extend, verts, faces, matids, uvs): + + last = None + for seg in self.segs: + seg.p_line = seg.make_offset(x_offset, last) + last = seg.p_line + + n_fences = len(self.segs) - 1 + + if n_fences < 0: + return + + sections = [] + + f = self.segs[0] + + # first step + if extend != 0 and f.p_line.length != 0: + t = -extend / self.segs[0].p_line.length + n = f.p_line.sized_normal(t, 1) + # n.p = f.lerp(x_offset) + sections.append((n, f.dz / f.p_line.length, f.z0 + f.dz * t)) + + # add first section + n = f.p_line.sized_normal(0, 1) + # n.p = f.lerp(x_offset) + sections.append((n, f.dz / f.p_line.length, f.z0)) + + for s, f in enumerate(self.segs): + if f.p_line.length == 0: + continue + if type(f).__name__ == 'CurvedFence': + n_s = int(max(1, abs(f.da) * 30 / pi - 1)) + for i in range(1, n_s + 1): + t = i / n_s + n = f.p_line.sized_normal(t, 1) + # n.p = f.lerp(x_offset) + sections.append((n, f.dz / f.p_line.length, f.z0 + f.dz * t)) + else: + n = f.p_line.sized_normal(1, 1) + # n.p = f.lerp(x_offset) + sections.append((n, f.dz / f.p_line.length, f.z0 + f.dz)) + + if extend != 0 and f.p_line.length != 0: + t = 1 + extend / self.segs[-1].p_line.length + n = f.p_line.sized_normal(t, 1) + # n.p = f.lerp(x_offset) + sections.append((n, f.dz / f.p_line.length, f.z0 + f.dz * t)) + + user_path_verts = len(sections) + offset = len(verts) + if user_path_verts > 0: + user_path_uv_v = [] + n, dz, z0 = sections[-1] + sections[-1] = (n, dz, z0) + n_sections = user_path_verts - 1 + n, dz, zl = sections[0] + p0 = n.p + v0 = n.v.normalized() + for s, section in enumerate(sections): + n, dz, zl = section + p1 = n.p + if s < n_sections: + v1 = sections[s + 1][0].v.normalized() + dir = (v0 + v1).normalized() + scale = min(10, 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1))))) + for p in profile: + # x, y = n.p + scale * (x_offset + p.x) * dir + x, y = n.p + scale * p.x * dir + z = zl + p.y + z_offset + verts.append((x, y, z)) + if s > 0: + user_path_uv_v.append((p1 - p0).length) + p0 = p1 + v0 = v1 + + # build faces using Panel + lofter = Lofter( + # closed_shape, index, x, y, idmat + True, + [i for i in range(len(profile))], + [p.x for p in profile], + [p.y for p in profile], + [idmat for i in range(len(profile))], + closed_path=False, + user_path_uv_v=user_path_uv_v, + user_path_verts=user_path_verts + ) + faces += lofter.faces(16, offset=offset, path_type='USER_DEFINED') + matids += lofter.mat(16, idmat, idmat, path_type='USER_DEFINED') + v = Vector((0, 0)) + uvs += lofter.uv(16, v, v, v, v, 0, v, 0, 0, path_type='USER_DEFINED') + + +def update(self, context): + self.update(context) + + +def update_manipulators(self, context): + self.update(context, manipulable_refresh=True) + + +def update_path(self, context): + self.update_path(context) + + +def update_type(self, context): + + d = self.find_datablock_in_selection(context) + + if d is not None and d.auto_update: + + d.auto_update = False + # find part index + idx = 0 + for i, part in enumerate(d.parts): + if part == self: + idx = i + break + part = d.parts[idx] + a0 = 0 + if idx > 0: + g = d.get_generator() + w0 = g.segs[idx - 1] + a0 = w0.straight(1).angle + if "C_" in self.type: + w = w0.straight_fence(part.a0, part.length) + else: + w = w0.curved_fence(part.a0, part.da, part.radius) + else: + g = FenceGenerator(None) + g.add_part(self) + w = g.segs[0] + + # w0 - w - w1 + dp = w.p1 - w.p0 + if "C_" in self.type: + part.radius = 0.5 * dp.length + part.da = pi + a0 = atan2(dp.y, dp.x) - pi / 2 - a0 + else: + part.length = dp.length + a0 = atan2(dp.y, dp.x) - a0 + + if a0 > pi: + a0 -= 2 * pi + if a0 < -pi: + a0 += 2 * pi + part.a0 = a0 + + if idx + 1 < d.n_parts: + # adjust rotation of next part + part1 = d.parts[idx + 1] + if "C_" in part.type: + a0 = part1.a0 - pi / 2 + else: + a0 = part1.a0 + w.straight(1).angle - atan2(dp.y, dp.x) + + if a0 > pi: + a0 -= 2 * pi + if a0 < -pi: + a0 += 2 * pi + part1.a0 = a0 + + d.auto_update = True + + +materials_enum = ( + ('0', 'Wood', '', 0), + ('1', 'Metal', '', 1), + ('2', 'Glass', '', 2) + ) + + +class archipack_fence_material(PropertyGroup): + index = EnumProperty( + items=materials_enum, + default='0', + update=update + ) + + def find_datablock_in_selection(self, context): + """ + find witch selected object this instance belongs to + provide support for "copy to selected" + """ + selected = [o for o in context.selected_objects] + for o in selected: + props = archipack_fence.datablock(o) + if props: + for part in props.rail_mat: + if part == self: + return props + return None + + def update(self, context): + props = self.find_datablock_in_selection(context) + if props is not None: + props.update(context) + + +class archipack_fence_part(PropertyGroup): + type = EnumProperty( + items=( + ('S_FENCE', 'Straight fence', '', 0), + ('C_FENCE', 'Curved fence', '', 1), + ), + default='S_FENCE', + update=update_type + ) + length = FloatProperty( + name="length", + min=0.01, + default=2.0, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + radius = FloatProperty( + name="radius", + min=0.01, + default=0.7, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + da = FloatProperty( + name="total angle", + min=-pi, + max=pi, + default=pi / 2, + subtype='ANGLE', unit='ROTATION', + update=update + ) + a0 = FloatProperty( + name="angle", + min=-2 * pi, + max=2 * pi, + default=0, + subtype='ANGLE', unit='ROTATION', + update=update + ) + dz = FloatProperty( + name="delta z", + default=0, + unit='LENGTH', subtype='DISTANCE' + ) + + manipulators = CollectionProperty(type=archipack_manipulator) + + def find_datablock_in_selection(self, context): + """ + find witch selected object this instance belongs to + provide support for "copy to selected" + """ + selected = [o for o in context.selected_objects] + for o in selected: + props = archipack_fence.datablock(o) + if props is not None: + for part in props.parts: + if part == self: + return props + return None + + def update(self, context, manipulable_refresh=False): + props = self.find_datablock_in_selection(context) + if props is not None: + props.update(context, manipulable_refresh) + + def draw(self, layout, context, index): + box = layout.box() + row = box.row() + row.prop(self, "type", text=str(index + 1)) + if self.type in ['C_FENCE']: + row = box.row() + row.prop(self, "radius") + row = box.row() + row.prop(self, "da") + else: + row = box.row() + row.prop(self, "length") + row = box.row() + row.prop(self, "a0") + + +class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): + + parts = CollectionProperty(type=archipack_fence_part) + user_defined_path = StringProperty( + name="user defined", + update=update_path + ) + user_defined_spline = IntProperty( + name="Spline index", + min=0, + default=0, + update=update_path + ) + user_defined_resolution = IntProperty( + name="resolution", + min=1, + max=128, + default=12, update=update_path + ) + n_parts = IntProperty( + name="parts", + min=1, + default=1, update=update_manipulators + ) + x_offset = FloatProperty( + name="x offset", + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + + radius = FloatProperty( + name="radius", + min=0.01, + default=0.7, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + da = FloatProperty( + name="angle", + min=-pi, + max=pi, + default=pi / 2, + subtype='ANGLE', unit='ROTATION', + update=update + ) + angle_limit = FloatProperty( + name="angle", + min=0, + max=2 * pi, + default=pi / 8, + subtype='ANGLE', unit='ROTATION', + update=update_manipulators + ) + shape = EnumProperty( + items=( + ('RECTANGLE', 'Straight', '', 0), + ('CIRCLE', 'Curved ', '', 1) + ), + default='RECTANGLE', + update=update + ) + post = BoolProperty( + name='enable', + default=True, + update=update + ) + post_spacing = FloatProperty( + name="spacing", + min=0.1, + default=1.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + post_x = FloatProperty( + name="width", + min=0.001, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + post_y = FloatProperty( + name="length", + min=0.001, max=1000, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + post_z = FloatProperty( + name="height", + min=0.001, + default=1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + post_alt = FloatProperty( + name="altitude", + default=0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + user_defined_post_enable = BoolProperty( + name="User", + update=update, + default=True + ) + user_defined_post = StringProperty( + name="user defined", + update=update + ) + idmat_post = EnumProperty( + name="Post", + items=materials_enum, + default='1', + update=update + ) + subs = BoolProperty( + name='enable', + default=False, + update=update + ) + subs_spacing = FloatProperty( + name="spacing", + min=0.05, + default=0.10, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_x = FloatProperty( + name="width", + min=0.001, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_y = FloatProperty( + name="length", + min=0.001, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_z = FloatProperty( + name="height", + min=0.001, + default=1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_alt = FloatProperty( + name="altitude", + default=0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_offset_x = FloatProperty( + name="offset", + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + subs_bottom = EnumProperty( + name="Bottom", + items=( + ('STEP', 'Follow step', '', 0), + ('LINEAR', 'Linear', '', 1), + ), + default='STEP', + update=update + ) + user_defined_subs_enable = BoolProperty( + name="User", + update=update, + default=True + ) + user_defined_subs = StringProperty( + name="user defined", + update=update + ) + idmat_subs = EnumProperty( + name="Subs", + items=materials_enum, + default='1', + update=update + ) + panel = BoolProperty( + name='enable', + default=True, + update=update + ) + panel_alt = FloatProperty( + name="altitude", + default=0.25, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + panel_x = FloatProperty( + name="width", + min=0.001, + default=0.01, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + panel_z = FloatProperty( + name="height", + min=0.001, + default=0.6, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + panel_dist = FloatProperty( + name="space", + min=0.001, + default=0.05, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + panel_offset_x = FloatProperty( + name="offset", + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + idmat_panel = EnumProperty( + name="Panels", + items=materials_enum, + default='2', + update=update + ) + rail = BoolProperty( + name="enable", + update=update, + default=False + ) + rail_n = IntProperty( + name="number", + default=1, + min=0, + max=31, + update=update + ) + rail_x = FloatVectorProperty( + name="width", + default=[ + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05 + ], + size=31, + min=0.001, + precision=2, step=1, + unit='LENGTH', + update=update + ) + rail_z = FloatVectorProperty( + name="height", + default=[ + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05 + ], + size=31, + min=0.001, + precision=2, step=1, + unit='LENGTH', + update=update + ) + rail_offset = FloatVectorProperty( + name="offset", + default=[ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0 + ], + size=31, + precision=2, step=1, + unit='LENGTH', + update=update + ) + rail_alt = FloatVectorProperty( + name="altitude", + default=[ + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 + ], + size=31, + precision=2, step=1, + unit='LENGTH', + update=update + ) + rail_mat = CollectionProperty(type=archipack_fence_material) + + handrail = BoolProperty( + name="enable", + update=update, + default=True + ) + handrail_offset = FloatProperty( + name="offset", + default=0.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_alt = FloatProperty( + name="altitude", + default=1.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_extend = FloatProperty( + name="extend", + min=0, + default=0.1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_slice = BoolProperty( + name='slice', + default=True, + update=update + ) + handrail_slice_right = BoolProperty( + name='slice', + default=True, + update=update + ) + handrail_profil = EnumProperty( + name="Profil", + items=( + ('SQUARE', 'Square', '', 0), + ('CIRCLE', 'Circle', '', 1), + ('COMPLEX', 'Circle over square', '', 2) + ), + default='SQUARE', + update=update + ) + handrail_x = FloatProperty( + name="width", + min=0.001, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_y = FloatProperty( + name="height", + min=0.001, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + handrail_radius = FloatProperty( + name="radius", + min=0.001, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + idmat_handrail = EnumProperty( + name="Handrail", + items=materials_enum, + default='0', + update=update + ) + + # UI layout related + parts_expand = BoolProperty( + default=False + ) + rail_expand = BoolProperty( + default=False + ) + idmats_expand = BoolProperty( + default=False + ) + handrail_expand = BoolProperty( + default=False + ) + post_expand = BoolProperty( + default=False + ) + panel_expand = BoolProperty( + default=False + ) + subs_expand = BoolProperty( + default=False + ) + + # Flag to prevent mesh update while making bulk changes over variables + # use : + # .auto_update = False + # bulk changes + # .auto_update = True + auto_update = BoolProperty( + options={'SKIP_SAVE'}, + default=True, + update=update_manipulators + ) + + def setup_manipulators(self): + + if len(self.manipulators) == 0: + s = self.manipulators.add() + s.prop1_name = "width" + s = self.manipulators.add() + s.prop1_name = "height" + s.normal = Vector((0, 1, 0)) + + for i in range(self.n_parts): + p = self.parts[i] + n_manips = len(p.manipulators) + if n_manips == 0: + s = p.manipulators.add() + s.type_key = "ANGLE" + s.prop1_name = "a0" + s = p.manipulators.add() + s.type_key = "SIZE" + s.prop1_name = "length" + s = p.manipulators.add() + # s.type_key = 'SNAP_POINT' + s.type_key = 'WALL_SNAP' + s.prop1_name = str(i) + s.prop2_name = 'post_z' + + def update_parts(self): + + # remove rails materials + for i in range(len(self.rail_mat), self.rail_n, -1): + self.rail_mat.remove(i - 1) + + # add rails + for i in range(len(self.rail_mat), self.rail_n): + self.rail_mat.add() + + # remove parts + for i in range(len(self.parts), self.n_parts, -1): + self.parts.remove(i - 1) + + # add parts + for i in range(len(self.parts), self.n_parts): + self.parts.add() + + self.setup_manipulators() + + def interpolate_bezier(self, pts, wM, p0, p1, resolution): + # straight segment, worth testing here + # since this can lower points count by a resolution factor + # use normalized to handle non linear t + if resolution == 0: + pts.append(wM * p0.co.to_3d()) + else: + v = (p1.co - p0.co).normalized() + d1 = (p0.handle_right - p0.co).normalized() + d2 = (p1.co - p1.handle_left).normalized() + if d1 == v and d2 == v: + pts.append(wM * p0.co.to_3d()) + else: + seg = interpolate_bezier(wM * p0.co, + wM * p0.handle_right, + wM * p1.handle_left, + wM * p1.co, + resolution + 1) + for i in range(resolution): + pts.append(seg[i].to_3d()) + + def from_spline(self, context, wM, resolution, spline): + + o = self.find_in_selection(context) + + if o is None: + return + + tM = wM.copy() + tM.row[0].normalize() + tM.row[1].normalize() + tM.row[2].normalize() + pts = [] + if spline.type == 'POLY': + pt = spline.points[0].co + pts = [wM * p.co.to_3d() for p in spline.points] + if spline.use_cyclic_u: + pts.append(pts[0]) + elif spline.type == 'BEZIER': + pt = spline.bezier_points[0].co + points = spline.bezier_points + for i in range(1, len(points)): + p0 = points[i - 1] + p1 = points[i] + self.interpolate_bezier(pts, wM, p0, p1, resolution) + if spline.use_cyclic_u: + p0 = points[-1] + p1 = points[0] + self.interpolate_bezier(pts, wM, p0, p1, resolution) + pts.append(pts[0]) + else: + pts.append(wM * points[-1].co) + auto_update = self.auto_update + self.auto_update = False + + self.n_parts = len(pts) - 1 + self.update_parts() + + p0 = pts.pop(0) + a0 = 0 + for i, p1 in enumerate(pts): + dp = p1 - p0 + da = atan2(dp.y, dp.x) - a0 + if da > pi: + da -= 2 * pi + if da < -pi: + da += 2 * pi + p = self.parts[i] + p.length = dp.to_2d().length + p.dz = dp.z + p.a0 = da + a0 += da + p0 = p1 + + self.auto_update = auto_update + + o.matrix_world = tM * Matrix([ + [1, 0, 0, pt.x], + [0, 1, 0, pt.y], + [0, 0, 1, pt.z], + [0, 0, 0, 1] + ]) + + def update_path(self, context): + path = context.scene.objects.get(self.user_defined_path) + if path is not None and path.type == 'CURVE': + splines = path.data.splines + if len(splines) > self.user_defined_spline: + self.from_spline( + context, + path.matrix_world, + self.user_defined_resolution, + splines[self.user_defined_spline]) + + def get_generator(self): + g = FenceGenerator(self.parts) + for part in self.parts: + # type, radius, da, length + g.add_part(part) + + g.set_offset(self.x_offset) + # param_t(da, part_length) + g.param_t(self.angle_limit, self.post_spacing) + return g + + def update(self, context, manipulable_refresh=False): + + o = self.find_in_selection(context, self.auto_update) + + if o is None: + return + + # clean up manipulators before any data model change + if manipulable_refresh: + self.manipulable_disable(context) + + self.update_parts() + + verts = [] + faces = [] + matids = [] + uvs = [] + + g = self.get_generator() + + # depth at bottom + # self.manipulators[1].set_pts([(0, 0, 0), (0, 0, self.height), (1, 0, 0)]) + + if self.user_defined_post_enable: + # user defined posts + user_def_post = context.scene.objects.get(self.user_defined_post) + if user_def_post is not None and user_def_post.type == 'MESH': + g.setup_user_defined_post(user_def_post, self.post_x, self.post_y, self.post_z) + + if self.post: + g.make_post(0.5 * self.post_x, 0.5 * self.post_y, self.post_z, + self.post_alt, self.x_offset, + int(self.idmat_post), verts, faces, matids, uvs) + + # reset user def posts + g.user_defined_post = None + + # user defined subs + if self.user_defined_subs_enable: + user_def_subs = context.scene.objects.get(self.user_defined_subs) + if user_def_subs is not None and user_def_subs.type == 'MESH': + g.setup_user_defined_post(user_def_subs, self.subs_x, self.subs_y, self.subs_z) + + if self.subs: + g.make_subs(0.5 * self.subs_x, 0.5 * self.subs_y, self.subs_z, + self.post_y, self.subs_alt, self.subs_spacing, + self.x_offset, self.subs_offset_x, int(self.idmat_subs), verts, faces, matids, uvs) + + g.user_defined_post = None + + if self.panel: + g.make_panels(0.5 * self.panel_x, self.panel_z, self.post_y, + self.panel_alt, self.panel_dist, self.x_offset, self.panel_offset_x, + int(self.idmat_panel), verts, faces, matids, uvs) + + if self.rail: + for i in range(self.rail_n): + x = 0.5 * self.rail_x[i] + y = self.rail_z[i] + rail = [Vector((-x, y)), Vector((-x, 0)), Vector((x, 0)), Vector((x, y))] + g.make_profile(rail, int(self.rail_mat[i].index), self.x_offset - self.rail_offset[i], + self.rail_alt[i], 0, verts, faces, matids, uvs) + + if self.handrail_profil == 'COMPLEX': + sx = self.handrail_x + sy = self.handrail_y + handrail = [Vector((sx * x, sy * y)) for x, y in [ + (-0.28, 1.83), (-0.355, 1.77), (-0.415, 1.695), (-0.46, 1.605), (-0.49, 1.51), (-0.5, 1.415), + (-0.49, 1.315), (-0.46, 1.225), (-0.415, 1.135), (-0.355, 1.06), (-0.28, 1.0), (-0.255, 0.925), + (-0.33, 0.855), (-0.5, 0.855), (-0.5, 0.0), (0.5, 0.0), (0.5, 0.855), (0.33, 0.855), (0.255, 0.925), + (0.28, 1.0), (0.355, 1.06), (0.415, 1.135), (0.46, 1.225), (0.49, 1.315), (0.5, 1.415), + (0.49, 1.51), (0.46, 1.605), (0.415, 1.695), (0.355, 1.77), (0.28, 1.83), (0.19, 1.875), + (0.1, 1.905), (0.0, 1.915), (-0.095, 1.905), (-0.19, 1.875)]] + + elif self.handrail_profil == 'SQUARE': + x = 0.5 * self.handrail_x + y = self.handrail_y + handrail = [Vector((-x, y)), Vector((-x, 0)), Vector((x, 0)), Vector((x, y))] + elif self.handrail_profil == 'CIRCLE': + r = self.handrail_radius + handrail = [Vector((r * sin(0.1 * -a * pi), r * (0.5 + cos(0.1 * -a * pi)))) for a in range(0, 20)] + + if self.handrail: + g.make_profile(handrail, int(self.idmat_handrail), self.x_offset - self.handrail_offset, + self.handrail_alt, self.handrail_extend, verts, faces, matids, uvs) + + bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs, weld=True, clean=True) + + # enable manipulators rebuild + if manipulable_refresh: + self.manipulable_refresh = True + + # restore context + self.restore_context(context) + + def manipulable_setup(self, context): + """ + NOTE: + this one assume context.active_object is the instance this + data belongs to, failing to do so will result in wrong + manipulators set on active object + """ + self.manipulable_disable(context) + + o = context.active_object + + self.setup_manipulators() + + for i, part in enumerate(self.parts): + if i >= self.n_parts: + break + + if i > 0: + # start angle + self.manip_stack.append(part.manipulators[0].setup(context, o, part)) + + # length / radius + angle + self.manip_stack.append(part.manipulators[1].setup(context, o, part)) + + # snap point + self.manip_stack.append(part.manipulators[2].setup(context, o, self)) + + for m in self.manipulators: + self.manip_stack.append(m.setup(context, o, self)) + + +class ARCHIPACK_PT_fence(Panel): + bl_idname = "ARCHIPACK_PT_fence" + bl_label = "Fence" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'ArchiPack' + + @classmethod + def poll(cls, context): + return archipack_fence.filter(context.active_object) + + def draw(self, context): + prop = archipack_fence.datablock(context.active_object) + if prop is None: + return + scene = context.scene + layout = self.layout + row = layout.row(align=True) + row.operator('archipack.fence_manipulate', icon='HAND') + box = layout.box() + # box.label(text="Styles") + row = box.row(align=True) + row.operator("archipack.fence_preset_menu", text=bpy.types.ARCHIPACK_OT_fence_preset_menu.bl_label) + row.operator("archipack.fence_preset", text="", icon='ZOOMIN') + row.operator("archipack.fence_preset", text="", icon='ZOOMOUT').remove_active = True + box = layout.box() + row = box.row(align=True) + row.operator("archipack.fence_curve_update", text="", icon='FILE_REFRESH') + row.prop_search(prop, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE') + if prop.user_defined_path is not "": + box.prop(prop, 'user_defined_spline') + box.prop(prop, 'user_defined_resolution') + box.prop(prop, 'angle_limit') + box.prop(prop, 'x_offset') + box = layout.box() + row = box.row() + if prop.parts_expand: + row.prop(prop, 'parts_expand', icon="TRIA_DOWN", icon_only=True, text="Parts", emboss=False) + box.prop(prop, 'n_parts') + for i, part in enumerate(prop.parts): + part.draw(layout, context, i) + else: + row.prop(prop, 'parts_expand', icon="TRIA_RIGHT", icon_only=True, text="Parts", emboss=False) + + box = layout.box() + row = box.row(align=True) + if prop.handrail_expand: + row.prop(prop, 'handrail_expand', icon="TRIA_DOWN", icon_only=True, text="Handrail", emboss=False) + else: + row.prop(prop, 'handrail_expand', icon="TRIA_RIGHT", icon_only=True, text="Handrail", emboss=False) + + row.prop(prop, 'handrail') + + if prop.handrail_expand: + box.prop(prop, 'handrail_alt') + box.prop(prop, 'handrail_offset') + box.prop(prop, 'handrail_extend') + box.prop(prop, 'handrail_profil') + if prop.handrail_profil != 'CIRCLE': + box.prop(prop, 'handrail_x') + box.prop(prop, 'handrail_y') + else: + box.prop(prop, 'handrail_radius') + row = box.row(align=True) + row.prop(prop, 'handrail_slice') + + box = layout.box() + row = box.row(align=True) + if prop.post_expand: + row.prop(prop, 'post_expand', icon="TRIA_DOWN", icon_only=True, text="Post", emboss=False) + else: + row.prop(prop, 'post_expand', icon="TRIA_RIGHT", icon_only=True, text="Post", emboss=False) + row.prop(prop, 'post') + if prop.post_expand: + box.prop(prop, 'post_spacing') + box.prop(prop, 'post_x') + box.prop(prop, 'post_y') + box.prop(prop, 'post_z') + box.prop(prop, 'post_alt') + row = box.row(align=True) + row.prop(prop, 'user_defined_post_enable', text="") + row.prop_search(prop, "user_defined_post", scene, "objects", text="") + + box = layout.box() + row = box.row(align=True) + if prop.subs_expand: + row.prop(prop, 'subs_expand', icon="TRIA_DOWN", icon_only=True, text="Subs", emboss=False) + else: + row.prop(prop, 'subs_expand', icon="TRIA_RIGHT", icon_only=True, text="Subs", emboss=False) + + row.prop(prop, 'subs') + if prop.subs_expand: + box.prop(prop, 'subs_spacing') + box.prop(prop, 'subs_x') + box.prop(prop, 'subs_y') + box.prop(prop, 'subs_z') + box.prop(prop, 'subs_alt') + box.prop(prop, 'subs_offset_x') + row = box.row(align=True) + row.prop(prop, 'user_defined_subs_enable', text="") + row.prop_search(prop, "user_defined_subs", scene, "objects", text="") + + box = layout.box() + row = box.row(align=True) + if prop.panel_expand: + row.prop(prop, 'panel_expand', icon="TRIA_DOWN", icon_only=True, text="Panels", emboss=False) + else: + row.prop(prop, 'panel_expand', icon="TRIA_RIGHT", icon_only=True, text="Panels", emboss=False) + row.prop(prop, 'panel') + if prop.panel_expand: + box.prop(prop, 'panel_dist') + box.prop(prop, 'panel_x') + box.prop(prop, 'panel_z') + box.prop(prop, 'panel_alt') + box.prop(prop, 'panel_offset_x') + + box = layout.box() + row = box.row(align=True) + if prop.rail_expand: + row.prop(prop, 'rail_expand', icon="TRIA_DOWN", icon_only=True, text="Rails", emboss=False) + else: + row.prop(prop, 'rail_expand', icon="TRIA_RIGHT", icon_only=True, text="Rails", emboss=False) + row.prop(prop, 'rail') + if prop.rail_expand: + box.prop(prop, 'rail_n') + for i in range(prop.rail_n): + box = layout.box() + box.label(text="Rail " + str(i + 1)) + box.prop(prop, 'rail_x', index=i) + box.prop(prop, 'rail_z', index=i) + box.prop(prop, 'rail_alt', index=i) + box.prop(prop, 'rail_offset', index=i) + box.prop(prop.rail_mat[i], 'index', text="") + + box = layout.box() + row = box.row() + + if prop.idmats_expand: + row.prop(prop, 'idmats_expand', icon="TRIA_DOWN", icon_only=True, text="Materials", emboss=False) + box.prop(prop, 'idmat_handrail') + box.prop(prop, 'idmat_panel') + box.prop(prop, 'idmat_post') + box.prop(prop, 'idmat_subs') + else: + row.prop(prop, 'idmats_expand', icon="TRIA_RIGHT", icon_only=True, text="Materials", emboss=False) + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_fence(ArchipackCreateTool, Operator): + bl_idname = "archipack.fence" + bl_label = "Fence" + bl_description = "Fence" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + def create(self, context): + m = bpy.data.meshes.new("Fence") + o = bpy.data.objects.new("Fence", m) + d = m.archipack_fence.add() + # make manipulators selectable + d.manipulable_selectable = True + context.scene.objects.link(o) + o.select = True + context.scene.objects.active = o + self.load_preset(d) + self.add_material(o) + return o + + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.location = bpy.context.scene.cursor_location + o.select = True + context.scene.objects.active = o + self.manipulate() + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ + +class ARCHIPACK_OT_fence_curve_update(Operator): + bl_idname = "archipack.fence_curve_update" + bl_label = "Fence curve update" + bl_description = "Update fence data from curve" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_fence.filter(context.active_object) + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def execute(self, context): + if context.mode == "OBJECT": + d = archipack_fence.datablock(context.active_object) + d.update_path(context) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_fence_from_curve(ArchipackCreateTool, Operator): + bl_idname = "archipack.fence_from_curve" + bl_label = "Fence curve" + bl_description = "Create a fence from a curve" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return context.active_object is not None and context.active_object.type == 'CURVE' + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def create(self, context): + o = None + curve = context.active_object + for i, spline in enumerate(curve.data.splines): + bpy.ops.archipack.fence('INVOKE_DEFAULT', auto_manipulate=False) + o = context.active_object + d = archipack_fence.datablock(o) + d.auto_update = False + d.user_defined_spline = i + d.user_defined_path = curve.name + d.auto_update = True + return o + + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + if o is not None: + o.select = True + context.scene.objects.active = o + # self.manipulate() + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + +# ------------------------------------------------------------------ +# Define operator class to manipulate object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_fence_manipulate(Operator): + bl_idname = "archipack.fence_manipulate" + bl_label = "Manipulate" + bl_description = "Manipulate" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_fence.filter(context.active_object) + + def invoke(self, context, event): + d = archipack_fence.datablock(context.active_object) + d.manipulable_invoke(context) + return {'FINISHED'} + + +# ------------------------------------------------------------------ +# Define operator class to load / save presets +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_fence_preset_menu(PresetMenuOperator, Operator): + bl_idname = "archipack.fence_preset_menu" + bl_label = "Fence Styles" + preset_subdir = "archipack_fence" + + +class ARCHIPACK_OT_fence_preset(ArchipackPreset, Operator): + """Add a Fence Preset""" + bl_idname = "archipack.fence_preset" + bl_label = "Add Fence Style" + preset_menu = "ARCHIPACK_OT_fence_preset_menu" + + @property + def blacklist(self): + return ['manipulators', 'n_parts', 'parts', 'user_defined_path', 'user_defined_spline'] + + +def register(): + bpy.utils.register_class(archipack_fence_material) + bpy.utils.register_class(archipack_fence_part) + bpy.utils.register_class(archipack_fence) + Mesh.archipack_fence = CollectionProperty(type=archipack_fence) + bpy.utils.register_class(ARCHIPACK_OT_fence_preset_menu) + bpy.utils.register_class(ARCHIPACK_PT_fence) + bpy.utils.register_class(ARCHIPACK_OT_fence) + bpy.utils.register_class(ARCHIPACK_OT_fence_preset) + bpy.utils.register_class(ARCHIPACK_OT_fence_manipulate) + bpy.utils.register_class(ARCHIPACK_OT_fence_from_curve) + bpy.utils.register_class(ARCHIPACK_OT_fence_curve_update) + + +def unregister(): + bpy.utils.unregister_class(archipack_fence_material) + bpy.utils.unregister_class(archipack_fence_part) + bpy.utils.unregister_class(archipack_fence) + del Mesh.archipack_fence + bpy.utils.unregister_class(ARCHIPACK_OT_fence_preset_menu) + bpy.utils.unregister_class(ARCHIPACK_PT_fence) + bpy.utils.unregister_class(ARCHIPACK_OT_fence) + bpy.utils.unregister_class(ARCHIPACK_OT_fence_preset) + bpy.utils.unregister_class(ARCHIPACK_OT_fence_manipulate) + bpy.utils.unregister_class(ARCHIPACK_OT_fence_from_curve) + bpy.utils.unregister_class(ARCHIPACK_OT_fence_curve_update) |