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