diff options
Diffstat (limited to 'archipack/archipack_floor.py')
-rw-r--r-- | archipack/archipack_floor.py | 2720 |
1 files changed, 1805 insertions, 915 deletions
diff --git a/archipack/archipack_floor.py b/archipack/archipack_floor.py index 7f02c2dd..29957716 100644 --- a/archipack/archipack_floor.py +++ b/archipack/archipack_floor.py @@ -14,826 +14,1221 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA. +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### # <pep8 compliant> # ---------------------------------------------------------- -# Base code inspired by JARCH Vis -# Original Author: Jacob Morris -# Author : Stephen Leger (s-leger) +# Author: Jacob Morris - Stephen Leger (s-leger) # ---------------------------------------------------------- import bpy from bpy.types import Operator, PropertyGroup, Mesh, Panel from bpy.props import ( - BoolProperty, EnumProperty, FloatProperty, - IntProperty, CollectionProperty + FloatProperty, CollectionProperty, StringProperty, + BoolProperty, IntProperty, EnumProperty ) -from random import uniform, randint -from math import tan, pi, sqrt -from mathutils import Vector +from mathutils import Vector, Matrix +from mathutils.geometry import interpolate_bezier +from random import uniform +from math import radians, cos, sin, pi, atan2, sqrt +import bmesh from .bmesh_utils import BmeshEdit as bmed -from .archipack_manipulator import Manipulable +from .archipack_2d import Line, Arc +from .archipack_manipulator import Manipulable, archipack_manipulator from .archipack_preset import ArchipackPreset, PresetMenuOperator from .archipack_object import ArchipackCreateTool, ArchipackObject +from .archipack_cutter import ( + CutAblePolygon, CutAbleGenerator, + ArchipackCutter, + ArchipackCutterPart + ) -def create_flooring(if_tile, over_width, over_length, b_width, b_length, b_length2, is_length_vary, - length_vary, num_boards, space_l, space_w, spacing, t_width, t_length, is_offset, offset, - is_ran_offset, offset_vary, t_width2, is_width_vary, width_vary, max_boards, is_ran_thickness, - ran_thickness, th, hb_dir): - - # create siding - if if_tile == "1": # Tiles Regular - return tile_regular(over_width, over_length, t_width, t_length, spacing, is_offset, offset, - is_ran_offset, offset_vary, th) - elif if_tile == "2": # Large + Small - return tile_ls(over_width, over_length, t_width, t_length, spacing, th) - elif if_tile == "3": # Large + Many Small - return tile_lms(over_width, over_length, t_width, spacing, th) - elif if_tile == "4": # Hexagonal - return tile_hexagon(over_width, over_length, t_width2, spacing, th) - elif if_tile == "21": # Planks - return wood_regular(over_width, over_length, b_width, b_length, space_l, space_w, - is_length_vary, length_vary, - is_width_vary, width_vary, - is_offset, offset, - is_ran_offset, offset_vary, - max_boards, is_ran_thickness, - ran_thickness, th) - elif if_tile == "22": # Parquet - return wood_parquet(over_width, over_length, b_width, spacing, num_boards, th) - elif if_tile == "23": # Herringbone Parquet - return wood_herringbone(over_width, over_length, b_width, b_length2, spacing, th, hb_dir, True) - elif if_tile == "24": # Herringbone - return wood_herringbone(over_width, over_length, b_width, b_length2, spacing, th, hb_dir, False) - - return [], [] - - -def wood_herringbone(ow, ol, bw, bl, s, th, hb_dir, stepped): - verts = [] - faces = [] - an_45 = 0.5 * sqrt(2) - x, y, z = 0.0, 0.0, th - x_off, y_off = 0.0, 0.0 # used for finding farther forwards points when stepped - ang_s = s * an_45 - s45 = s / an_45 - - # step variables - if stepped: - x_off = an_45 * bw - y_off = an_45 * bw - - wid_off = an_45 * bl # offset from one end of the board to the other inline with width - len_off = an_45 * bl # offset from one end of the board to the other inline with length - w = bw / an_45 # width adjusted for 45 degree rotation - - # figure out starting position - if hb_dir == "1": - y = -wid_off - - elif hb_dir == "2": - x = ow - y = ol + wid_off - - elif hb_dir == "3": - x = -wid_off - y = ol - - elif hb_dir == "4": - x = ow + wid_off - - # loop going forwards - while (hb_dir == "1" and y < ol + wid_off) or (hb_dir == "2" and y > 0 - wid_off) or \ - (hb_dir == "3" and x < ow + wid_off) or (hb_dir == "4" and x > 0 - wid_off): - going_forwards = True - - # loop going right - while (hb_dir == "1" and x < ow) or (hb_dir == "2" and x > 0) or (hb_dir == "3" and y > 0 - y_off) or \ - (hb_dir == "4" and y < ol + y_off): - p = len(verts) - - # add verts - # forwards - verts.append((x, y, z)) - - if hb_dir == "1": - - if stepped and x != 0: - verts.append((x - x_off, y + y_off, z)) - else: - verts.append((x, y + w, z)) - - if going_forwards: - y += wid_off - else: - y -= wid_off - x += len_off - - verts.append((x, y, z)) - if stepped: - verts.append((x - x_off, y + y_off, z)) - x -= x_off - ang_s - if going_forwards: - y += y_off + ang_s - else: - y -= y_off + ang_s - else: - verts.append((x, y + w, z)) - x += s - - # backwards - elif hb_dir == "2": - - if stepped and x != ow: - verts.append((x + x_off, y - y_off, z)) - else: - verts.append((x, y - w, z)) - - if going_forwards: - y -= wid_off - else: - y += wid_off - x -= len_off - - verts.append((x, y, z)) - if stepped: - verts.append((x + x_off, y - y_off, z)) - x += x_off - ang_s - if going_forwards: - y -= y_off + ang_s - else: - y += y_off + ang_s - else: - verts.append((x, y - w, z)) - x -= s - # right - elif hb_dir == "3": - - if stepped and y != ol: - verts.append((x + y_off, y + x_off, z)) - else: - verts.append((x + w, y, z)) - - if going_forwards: - x += wid_off - else: - x -= wid_off - y -= len_off - - verts.append((x, y, z)) - if stepped: - verts.append((x + y_off, y + x_off, z)) - y += x_off - ang_s - if going_forwards: - x += y_off + ang_s - else: - x -= y_off + ang_s - else: - verts.append((x + w, y, z)) - y -= s - # left - else: - - if stepped and y != 0: - verts.append((x - y_off, y - x_off, z)) - else: - verts.append((x - w, y, z)) - - if going_forwards: - x -= wid_off - else: - x += wid_off - y += len_off - - verts.append((x, y, z)) - if stepped: - verts.append((x - y_off, y - x_off, z)) - y -= x_off - ang_s - if going_forwards: - x -= y_off + ang_s - else: - x += y_off + ang_s - else: - verts.append((x - w, y, z)) - y += s - - # faces - faces.append((p, p + 2, p + 3, p + 1)) - - # flip going_right - going_forwards = not going_forwards - x_off *= -1 - - # if not in forwards position, then move back before adjusting values for next row - if not going_forwards: - x_off = abs(x_off) - if hb_dir == "1": - y -= wid_off - if stepped: - y -= y_off + ang_s - elif hb_dir == "2": - y += wid_off - if stepped: - y += y_off + ang_s - elif hb_dir == "3": - x -= wid_off - if stepped: - x -= y_off + ang_s - else: - x += wid_off - if stepped: - x += y_off + ang_s - - # adjust forwards - if hb_dir == "1": - y += w + s45 - x = 0 - elif hb_dir == "2": - y -= w + s45 - x = ow - elif hb_dir == "3": - x += w + s45 - y = ol - else: - x -= w + s45 - y = 0 - - return verts, faces - +# ------------------------------------------------------------------ +# Define property class to store object parameters and update mesh +# ------------------------------------------------------------------ -def tile_ls(ow, ol, tw, tl, s, z): - """ - pattern: - _____ - | |_| - |___| - x and y are axis of big one - """ +class Floor(): - verts = [] - faces = [] + def __init__(self): + # self.colour_inactive = (1, 1, 1, 1) + pass - # big half size - hw = (tw / 2) - (s / 2) - hl = (tl / 2) - (s / 2) - # small half size - hws = (tw / 4) - (s / 2) - hls = (tl / 4) - (s / 2) + def set_offset(self, offset, last=None): + """ + Offset line and compute intersection point + between segments + """ + self.line = self.make_offset(offset, last) - # small, offset from big x,y - xo = 0.75 * tw - yo = 0.25 * tl + def straight_floor(self, a0, length): + s = self.straight(length).rotate(a0) + return StraightFloor(s.p, s.v) - # offset for pattern - rx = 2.5 * tw - ry = 0.5 * tl + def curved_floor(self, a0, da, radius): + n = self.normal(1).rotate(a0).scale(radius) + if da < 0: + n.v = -n.v + a0 = n.angle + c = n.p - n.v + return CurvedFloor(c, radius, a0, da) - # width and a half of big - ow_x = ow + 0.5 * tw - ol_y = ol + 0.5 * tl - # start pattern with big one - x = tw - y = -tl +class StraightFloor(Floor, Line): - while y < ol_y: + def __init__(self, p, v): + Line.__init__(self, p, v) + Floor.__init__(self) - while x < ow_x: - p = len(verts) +class CurvedFloor(Floor, Arc): - # Large - x0 = max(0, x - hw) - y0 = max(0, y - hl) - x1 = min(ow, x + hw) - y1 = min(ol, y + hl) - if y1 > 0: - if x1 > 0 and x0 < ow and y0 < ol: + def __init__(self, c, radius, a0, da): + Arc.__init__(self, c, radius, a0, da) + Floor.__init__(self) - verts.extend([(x0, y1, z), (x1, y1, z), (x1, y0, z), (x0, y0, z)]) - faces.append((p, p + 1, p + 2, p + 3)) - p = len(verts) - # Small - x0 = x + xo - hws - y0 = y + yo - hls - x1 = min(ow, x + xo + hws) +class FloorGenerator(CutAblePolygon, CutAbleGenerator): - if x1 > 0 and x0 < ow and y0 < ol: + def __init__(self, parts): + self.parts = parts + self.segs = [] + self.holes = [] + self.convex = True + self.xsize = 0 - y1 = min(ol, y + yo + hls) - verts.extend([(x0, y1, z), (x1, y1, z), (x1, y0, z), (x0, y0, z)]) - faces.append((p, p + 1, p + 2, p + 3)) + def add_part(self, part): - x += rx - - y += ry - x = x % rx - tw - if x < -tw: - x += rx - - return verts, faces + if len(self.segs) < 1: + s = None + else: + s = self.segs[-1] + # start a new floor + if s is None: + if part.type == 'S_SEG': + p = Vector((0, 0)) + v = part.length * Vector((cos(part.a0), sin(part.a0))) + s = StraightFloor(p, v) + elif part.type == 'C_SEG': + c = -part.radius * Vector((cos(part.a0), sin(part.a0))) + s = CurvedFloor(c, part.radius, part.a0, part.da) + else: + if part.type == 'S_SEG': + s = s.straight_floor(part.a0, part.length) + elif part.type == 'C_SEG': + s = s.curved_floor(part.a0, part.da, part.radius) + + self.segs.append(s) + self.last_type = part.type + + def set_offset(self): + last = None + for i, seg in enumerate(self.segs): + seg.set_offset(self.parts[i].offset, last) + last = seg.line + + def close(self, closed): + # Make last segment implicit closing one + if closed: + part = self.parts[-1] + w = self.segs[-1] + dp = self.segs[0].p0 - self.segs[-1].p0 + if "C_" in part.type: + dw = (w.p1 - w.p0) + w.r = part.radius / dw.length * dp.length + # angle pt - p0 - angle p0 p1 + da = atan2(dp.y, dp.x) - atan2(dw.y, dw.x) + a0 = w.a0 + da + if a0 > pi: + a0 -= 2 * pi + if a0 < -pi: + a0 += 2 * pi + w.a0 = a0 + else: + w.v = dp + + if len(self.segs) > 1: + w.line = w.make_offset(self.parts[-1].offset, self.segs[-2].line) + + p1 = self.segs[0].line.p1 + self.segs[0].line = self.segs[0].make_offset(self.parts[0].offset, w.line) + self.segs[0].line.p1 = p1 + + def locate_manipulators(self): + """ + setup manipulators + """ + for i, f in enumerate(self.segs): + + manipulators = self.parts[i].manipulators + p0 = f.p0.to_3d() + p1 = f.p1.to_3d() + # angle from last to current segment + if i > 0: + v0 = self.segs[i - 1].straight(-1, 1).v.to_3d() + v1 = f.straight(1, 0).v.to_3d() + manipulators[0].set_pts([p0, v0, v1]) + + if type(f).__name__ == "StraightFloor": + # segment length + manipulators[1].type_key = 'SIZE' + manipulators[1].prop1_name = "length" + manipulators[1].set_pts([p0, p1, (1, 0, 0)]) + else: + # segment radius + angle + v0 = (f.p0 - f.c).to_3d() + v1 = (f.p1 - f.c).to_3d() + manipulators[1].type_key = 'ARC_ANGLE_RADIUS' + manipulators[1].prop1_name = "da" + manipulators[1].prop2_name = "radius" + manipulators[1].set_pts([f.c.to_3d(), v0, v1]) + + # snap manipulator, dont change index ! + manipulators[2].set_pts([p0, p1, (1, 0, 0)]) + # dumb segment id + manipulators[3].set_pts([p0, p1, (1, 0, 0)]) + + def get_verts(self, verts): + for s in self.segs: + if "Curved" in type(s).__name__: + for i in range(16): + # x, y = floor.line.lerp(i / 16) + verts.append(s.lerp(i / 16).to_3d()) + else: + # x, y = s.line.p0 + verts.append(s.p0.to_3d()) + """ + for i in range(33): + x, y = floor.line.lerp(i / 32) + verts.append((x, y, 0)) + """ + + def rotate(self, idx_from, a): + """ + apply rotation to all following segs + """ + self.segs[idx_from].rotate(a) + ca = cos(a) + sa = sin(a) + rM = Matrix([ + [ca, -sa], + [sa, ca] + ]) + # rotation center + p0 = self.segs[idx_from].p0 + for i in range(idx_from + 1, len(self.segs)): + seg = self.segs[i] + # rotate seg + seg.rotate(a) + # rotate delta from rotation center to segment start + dp = rM * (seg.p0 - p0) + seg.translate(dp) + + def translate(self, idx_from, dp): + """ + apply translation to all following segs + """ + self.segs[idx_from].p1 += dp + for i in range(idx_from + 1, len(self.segs)): + self.segs[i].translate(dp) + def draw(self, context): + """ + draw generator using gl + """ + for seg in self.segs: + seg.draw(context, render=False) + + def limits(self): + x_size = [s.p0.x for s in self.segs] + y_size = [s.p0.y for s in self.segs] + self.xmin = min(x_size) + self.xmax = max(x_size) + self.xsize = self.xmax - self.xmin + self.ymin = min(y_size) + self.ymax = max(y_size) + self.ysize = self.ymax - self.ymin + + def cut(self, context, o): + """ + either external or holes cuts + """ + self.limits() + self.is_convex() + for b in o.children: + d = archipack_floor_cutter.datablock(b) + if d is not None: + g = d.ensure_direction() + g.change_coordsys(b.matrix_world, o.matrix_world) + self.slice(g) + + def floor(self, context, o, d): + + verts, faces, matids, uvs = [], [], [], [] + + if d.bevel: + bevel = d.bevel_amount + else: + bevel = 0 -def tile_hexagon(ow, ol, tw, s, z): - verts = [] - faces = [] - offset = False + if d.add_grout: + thickness = min(d.thickness - d.mortar_depth, d.thickness - 0.0001) + bottom = min(d.thickness - (d.mortar_depth + bevel), d.thickness - 0.0001) + else: + thickness = d.thickness + bottom = 0 - w = tw / 2 - y = 0.0 - h = w * tan(pi / 6) - r = sqrt((w * w) + (h * h)) + self.top = d.thickness - while y < ol + tw: - if not offset: - x = 0.0 - else: - x = w + (s / 2) + self.generate_pattern(d, verts, faces, matids, uvs) + bm = bmed.buildmesh( + context, o, verts, faces, matids=matids, uvs=uvs, + weld=False, clean=False, auto_smooth=True, temporary=True) - while x < ow + tw: - p = len(verts) + self.cut_holes(bm, self) + self.cut_boundary(bm, self) - verts.extend([(x + w, y + h, z), (x, y + r, z), (x - w, y + h, z), - (x - w, y - h, z), (x, y - r, z), (x + w, y - h, z)]) - faces.extend([(p, p + 1, p + 2, p + 3), (p + 3, p + 4, p + 5, p)]) + bmesh.ops.dissolve_limit(bm, + angle_limit=0.01, + use_dissolve_boundaries=False, + verts=bm.verts, + edges=bm.edges, + delimit=1) - x += tw + s + bm.verts.ensure_lookup_table() - y += r + h + s - offset = not offset + # solidify and floor bottom + geom = bm.faces[:] + verts = bm.verts[:] + edges = bm.edges[:] + bmesh.ops.solidify(bm, geom=geom, thickness=0.0001) + for v in verts: + v.co.z = bottom - return verts, faces + # bevel + if d.bevel: + for v in bm.verts: + v.select = True + for v in verts: + v.select = False + for v in bm.edges: + v.select = True + for v in edges: + v.select = False + geom = [v for v in bm.verts if v.select] + geom.extend([v for v in bm.edges if v.select]) + bmesh.ops.bevel(bm, + geom=geom, + offset=d.bevel_amount, + offset_type=0, + segments=1, # d.bevel_res + profile=0.5, + vertex_only=False, + clamp_overlap=False, + material=-1) + + bm.to_mesh(o.data) + bm.free() + # Grout + if d.add_grout: + verts = [] + self.get_verts(verts) + # + bm = bmesh.new() + for v in verts: + bm.verts.new(v) + bm.verts.ensure_lookup_table() + for i in range(1, len(verts)): + bm.edges.new((bm.verts[i - 1], bm.verts[i])) + bm.edges.new((bm.verts[-1], bm.verts[0])) + bm.edges.ensure_lookup_table() + bmesh.ops.contextual_create(bm, geom=bm.edges) + + self.cut_holes(bm, self) + self.cut_boundary(bm, self) + + bmesh.ops.dissolve_limit(bm, + angle_limit=0.01, + use_dissolve_boundaries=False, + verts=bm.verts, + edges=bm.edges, + delimit=1) + + bm.verts.ensure_lookup_table() + + geom = bm.faces[:] + bmesh.ops.solidify(bm, geom=geom, thickness=thickness) + bmed.bmesh_join(context, o, [bm], normal_update=True) + + bpy.ops.object.mode_set(mode='OBJECT') + + # --------------------------------------------------- + # Patterns + # --------------------------------------------------- + + def regular_tile(self, d, verts, faces, matids, uvs): + """ + ____ ____ ____ + | || || | Regular tile, rows can be offset, either manually or randomly + |____||____||____| + ____ ____ ____ + | || || | + |____||____||____| + """ + off = False + o = 1 / (100 / d.offset) if d.offset != 0 else 0 + y = self.ymin + + while y < self.ymax: + x = self.xmin + tl2 = d.tile_length + if y < self.ymax < y + d.tile_length: + tl2 = self.ymax - y + + while x < self.xmax: + tw2 = d.tile_width + + if x < self.xmax < x + d.tile_width: + tw2 = self.xmax - x + elif x == self.xmin and off and not d.random_offset: + tw2 = d.tile_width * o + elif x == self.xmin and d.random_offset: + v = d.tile_width * d.offset_variance * 0.0049 + tw2 = (d.tile_width / 2) + uniform(-v, v) + + self.add_plane(d, verts, faces, matids, uvs, x, y, tw2, tl2) + x += tw2 + d.spacing + + y += tl2 + d.spacing + off = not off + + def hopscotch(self, d, verts, faces, matids, uvs): + """ + ____ _ Large tile, plus small one on top right corner + | ||_| + |____| ____ _ But shifted up so next large one is right below previous small one + | ||_| + |____| + """ + sp = d.spacing + + # movement variables + row = 0 + + tw = d.tile_width + tl = d.tile_length + s_tw = (tw - sp) / 2 # small tile width + s_tl = (tl - sp) / 2 # small tile length + y = self.ymin - s_tl + + pre_y = y + while y < self.ymax + s_tl or (row == 2 and y - sp < self.ymax): + x = self.xmin + step_back = True + + if row == 1: # row start indented slightly + x = self.xmin + s_tw + sp + + while x < self.xmax: + if row == 0 or row == 1: + # adjust for if there is a need to cut off the bottom of the tile + if y < self.ymin - s_tl: + self.add_plane(d, verts, faces, matids, uvs, x, y, tw, tl + y - self.ymin) # large one + else: + self.add_plane(d, verts, faces, matids, uvs, x, y, tw, tl) # large one -def tile_lms(ow, ol, tw, s, z): - verts = [] - faces = [] - small = True + self.add_plane(d, verts, faces, matids, uvs, x + tw + sp, y + s_tl + sp, s_tw, s_tl) # small one - y = 0.0 - ref = (tw - s) / 2 + if step_back: + x += tw + sp + y -= s_tl + sp + else: + x += tw + s_tw + 2 * sp + y += s_tl + sp - while y < ol: - x = 0.0 - large = False - while x < ow: - if small: - x1 = min(x + ref, ow) - y1 = min(y + ref, ol) - p = len(verts) - verts.extend([(x, y1, z), (x, y, z)]) - verts.extend([(x1, y1, z), (x1, y, z)]) - faces.append((p, p + 1, p + 3, p + 2)) - x += ref - else: - if not large: - x1 = min(x + ref, ow) - for i in range(2): - y0 = y + i * (ref + s) - if x < ow and y0 < ol: - y1 = min(y0 + ref, ol) - p = len(verts) - verts.extend([(x, y1, z), (x, y0, z)]) - verts.extend([(x1, y1, z), (x1, y0, z)]) - faces.append((p, p + 1, p + 3, p + 2)) - x += ref + step_back = not step_back else: - x1 = min(x + tw, ow) - y1 = min(y + tw, ol) - p = len(verts) - verts.extend([(x, y1, z), (x, y, z)]) - verts.extend([(x1, y1, z), (x1, y, z)]) - faces.append((p, p + 1, p + 3, p + 2)) - x += tw - large = not large - x += s - if small: - y += ref + s - else: - y += tw + s - small = not small - - return verts, faces + if x == self.xmin: # half width for starting position + self.add_plane(d, verts, faces, matids, uvs, x, y, s_tw, tl) # large one + # small one on right + self.add_plane(d, verts, faces, matids, uvs, x + s_tw + sp, y + s_tl + sp, s_tw, s_tl) + # small one on bottom + self.add_plane(d, verts, faces, matids, uvs, x, y - sp - s_tl, s_tw, s_tl) + x += (2 * s_tw) + tw + (3 * sp) + else: + self.add_plane(d, verts, faces, matids, uvs, x, y, tw, tl) # large one + # small one on right + self.add_plane(d, verts, faces, matids, uvs, x + tw + sp, y + s_tl + sp, s_tw, s_tl) + x += (2 * tw) + (3 * sp) + s_tw + if row == 0 or row == 2: + y = pre_y + tl + sp + else: + y = pre_y + s_tl + sp + pre_y = y + + row = (row + 1) % 3 # keep wrapping rows + + def stepping_stone(self, d, verts, faces, matids, uvs): + """ + ____ __ ____ + | ||__|| | Row of large one, then two small ones stacked beside it + | | __ | | + |____||__||____| + __ __ __ __ + |__||__||__||__| Row of smalls + """ + sp = d.spacing + y = self.ymin + row = 0 + + tw = d.tile_width + tl = d.tile_length + s_tw = (tw - sp) / 2 + s_tl = (tl - sp) / 2 + + while y < self.ymax: + x = self.xmin + + while x < self.xmax: + if row == 0: # large one then two small ones stacked beside it + self.add_plane(d, verts, faces, matids, uvs, x, y, tw, tl) + self.add_plane(d, verts, faces, matids, uvs, x + tw + sp, y, s_tw, s_tl,) + self.add_plane(d, verts, faces, matids, uvs, x + tw + sp, y + s_tl + sp, s_tw, s_tl) + x += tw + s_tw + (2 * sp) + else: # row of small ones + self.add_plane(d, verts, faces, matids, uvs, x, y, s_tw, s_tl) + self.add_plane(d, verts, faces, matids, uvs, x + s_tw + sp, y, s_tw, s_tl) + x += tw + sp + + if row == 0: + y += tl + sp + else: + y += s_tl + sp + + row = (row + 1) % 2 + + def hexagon(self, d, verts, faces, matids, uvs): + """ + __ Hexagon tiles + / \ + \___/ + """ + sp = d.spacing + width = d.tile_width + dia = (width / 2) / cos(radians(30)) + # top of current, half way up next, vertical spacing component + vertical_spacing = dia * (1 + sin(radians(30))) + (sp * sin(radians(60))) # center of one row to next row + da = pi / 3 + base_points = [(sin(i * da), cos(i * da)) for i in range(6)] + + y = self.ymin + offset = False + while y - width / 2 < self.ymax: # place tile as long as bottom is still within bounds + if offset: + x = self.xmin + width / 2 + else: + x = self.xmin - sp / 2 -def tile_regular(ow, ol, tw, tl, s, is_offset, offset, is_ran_offset, offset_vary, z): - verts = [] - faces = [] - off = False - o = 1 / (100 / offset) - y = 0.0 + while x - width / 2 < self.xmax: # place tile as long as left is still within bounds + f = len(verts) - while y < ol: + if d.vary_thickness and d.thickness_variance > 0: + v = d.thickness / 100 * d.thickness_variance + z = uniform(self.top, self.top + v) + else: + z = self.top + + for pt in base_points: + verts.append((dia * pt[0] + x, dia * pt[1] + y, z)) + + faces.append([f] + [i for i in range(f + 1, len(verts))]) + uvs.append(base_points) + self.add_matid(d, matids) + + x += width + sp + + y += vertical_spacing + offset = not offset + + def windmill(self, d, verts, faces, matids, uvs): + """ + __ ____ + | ||____| This also has a square one in the middle, totaling 5 tiles per pattern + |__| __ + ____ | | + |____||__| + """ + sp = d.spacing + + tw = d.tile_width + tl = d.tile_length + s_tw = (tw - sp) / 2 + s_tl = (tl - sp) / 2 + + y = self.ymin + while y < self.ymax: + x = self.xmin + + while x < self.xmax: + self.add_plane(d, verts, faces, matids, uvs, x, y, tw, s_tl) # bottom + self.add_plane(d, verts, faces, matids, uvs, x + tw + sp, y, s_tw, tl, rotate_uv=True) # right + self.add_plane(d, verts, faces, matids, uvs, x + s_tw + sp, y + tl + sp, tw, s_tl) # top + self.add_plane(d, verts, faces, matids, uvs, x, y + s_tl + sp, s_tw, tl, rotate_uv=True) # left + self.add_plane(d, verts, faces, matids, uvs, x + s_tw + sp, y + s_tl + sp, s_tw, s_tl) # center + + x += tw + s_tw + (2 * sp) + y += tl + s_tl + (2 * sp) + + def boards(self, d, verts, faces, matids, uvs): + """ + ||| Typical wood boards + ||| + """ + x = self.xmin + bw, bl = d.board_width, d.board_length + off = False + o = 1 / (100 / d.offset) if d.offset != 0 else 0 + + while x < self.xmax: + if d.vary_width: + v = bw * (d.width_variance / 100) * 0.99 + bw2 = bw + uniform(-v, v) + else: + bw2 = bw + + if bw2 + x > self.xmax: + bw2 = self.xmax - x + y = self.ymin + + counter = 1 + while y < self.ymax: + bl2 = bl + if d.vary_length: + v = bl * (d.length_variance / 100) * 0.99 + bl2 = bl + uniform(-v, v) + elif y == self.ymin and off and not d.random_offset: + bl2 = bl * o + elif y == self.ymin and d.random_offset: + v = bl * d.offset_variance * 0.0049 + bl2 = (bl / 2) + uniform(-v, v) + + if (counter >= d.max_boards and d.vary_length) or y + bl2 > self.ymax: + bl2 = self.ymax - y + + self.add_plane(d, verts, faces, matids, uvs, x, y, bw2, bl2, rotate_uv=True) + y += bl2 + d.length_spacing + counter += 1 + off = not off + x += bw2 + d.width_spacing + + def square_parquet(self, d, verts, faces, matids, uvs): + """ + ||--||-- Alternating groups oriented either horizontally, or forwards and backwards. + ||--||-- self.spacing is used because it is the same spacing for width and length + --||--|| Board width is calculated using number of boards and the length. + --||--|| + """ + x = self.xmin + start_orient_length = True + + # figure board width + bl = d.short_board_length + bw = (bl - (d.boards_in_group - 1) * d.spacing) / d.boards_in_group + while x < self.xmax: + y = self.ymin + orient_length = start_orient_length + while y < self.ymax: + + if orient_length: + start_x = x + + for i in range(d.boards_in_group): + if x < self.xmax and y < self.ymax: + self.add_plane(d, verts, faces, matids, uvs, x, y, bw, bl, rotate_uv=True) + x += bw + d.spacing + + x = start_x + y += bl + d.spacing - tw2 = 0 - if is_offset: - if is_ran_offset: - v = tw * 0.0049 * offset_vary - tw2 = uniform((tw / 2) - v, (tw / 2) + v) - elif off: - tw2 = o * tw - x = -tw2 - y1 = min(ol, y + tl) + else: + for i in range(d.boards_in_group): + if x < self.xmax and y < self.ymax: + self.add_plane(d, verts, faces, matids, uvs, x, y, bl, bw) + y += bw + d.spacing - while x < ow: - p = len(verts) - x0 = max(0, x) - x1 = min(ow, x + tw) + orient_length = not orient_length - verts.extend([(x0, y1, z), (x0, y, z), (x1, y, z), (x1, y1, z)]) - faces.append((p, p + 1, p + 2, p + 3)) - x = x1 + s + start_orient_length = not start_orient_length + x += bl + d.spacing - y += tl + s - off = not off + def herringbone(self, d, verts, faces, matids, uvs): + """ + Boards are at 45 degree angle, in chevron pattern, ends are angled + """ + width_dif = d.board_width / cos(radians(45)) + x_dif = d.short_board_length * cos(radians(45)) + y_dif = d.short_board_length * sin(radians(45)) + total_y_dif = width_dif + y_dif + sp_dif = d.spacing / cos(radians(45)) - return verts, faces + y = self.ymin - y_dif + while y < self.ymax: + x = self.xmin + while x < self.xmax: + # left side -def wood_parquet(ow, ol, bw, s, num_boards, z): - verts = [] - faces = [] - x = 0.0 + self.add_face(d, verts, faces, matids, uvs, + (x, y, 0), (x + x_dif, y + y_dif, 0), + (x + x_dif, y + total_y_dif, 0), (x, y + width_dif, 0)) - start_orient_length = True + x += x_dif + d.spacing - # figure board length - bl = (bw * num_boards) + (s * (num_boards - 1)) - while x < ow: + # right side + if x < self.xmax: + self.add_face(d, verts, faces, matids, uvs, + (x, y + y_dif, 0), (x + x_dif, y, 0), + (x + x_dif, y + width_dif, 0), (x, y + total_y_dif, 0)) + x += x_dif + d.spacing - y = 0.0 + y += width_dif + sp_dif # adjust spacing amount for 45 degree angle - orient_length = start_orient_length + def herringbone_parquet(self, d, verts, faces, matids, uvs): + """ + Boards are at 45 degree angle, in chevron pattern, ends are square, not angled + """ - while y < ol: + an_45 = 0.5 * sqrt(2) - if orient_length: - y0 = y - y1 = min(y + bl, ol) + x_dif = d.short_board_length * an_45 + y_dif = d.short_board_length * an_45 + y_dif_45 = d.board_width * an_45 + x_dif_45 = d.board_width * an_45 + total_y_dif = y_dif + y_dif_45 - for i in range(num_boards): + sp_dif = (d.spacing / an_45) / 2 # divide by two since it is used for both x and y + width_dif = d.board_width / an_45 - bx = x + i * (bw + s) + y = self.ymin - y_dif + while y - y_dif_45 < self.ymax: # continue as long as bottom left corner is still good + x = self.xmin - if bx < ow and y < ol: + while x - x_dif_45 < self.xmax: # continue as long as top left corner is still good + # left side - # make sure board should be placed - x0 = bx - x1 = min(bx + bw, ow) + self.add_face(d, verts, faces, matids, uvs, + (x, y, 0), + (x + x_dif, y + y_dif, 0), + (x + x_dif - x_dif_45, y + total_y_dif, 0), + (x - x_dif_45, y + y_dif_45, 0)) - p = len(verts) - verts.extend([(x0, y0, z), (x1, y0, z), (x1, y1, z), (x0, y1, z)]) - faces.append((p, p + 1, p + 2, p + 3)) + x += x_dif - x_dif_45 + sp_dif + y0 = y + y_dif - y_dif_45 - sp_dif - else: - x0 = x - x1 = min(x + bl, ow) + if x < self.xmax: + self.add_face(d, verts, faces, matids, uvs, + (x, y0, 0), + (x + x_dif, y0 - y_dif, 0), + (x + x_dif + x_dif_45, y0 - y_dif + y_dif_45, 0), + (x + x_dif_45, y0 + y_dif_45, 0)) - for i in range(num_boards): + x += x_dif + x_dif_45 + sp_dif - by = y + i * (bw + s) + else: # we didn't place the right board, so step ahead far enough the the while loop for x breaks + break - if x < ow and by < ol: - y0 = by - y1 = min(by + bw, ol) - p = len(verts) + y += width_dif + (2 * sp_dif) - verts.extend([(x0, y0, z), (x1, y0, z), (x1, y1, z), (x0, y1, z)]) - faces.append((p, p + 1, p + 2, p + 3)) + def add_matid(self, d, matids): + if d.vary_materials: + matid = uniform(1, d.matid) + else: + matid = d.matid + matids.append(matid) + + def add_plane(self, d, verts, faces, matids, uvs, x, y, w, l, rotate_uv=False): + """ + Adds vertices and faces for a place, clip to outer boundaries if clip is True + :param x: start x position + :param y: start y position + :param w: width (in x direction) + :param l: length (in y direction) + """ + + x1 = x + w + y1 = y + l + + if d.vary_thickness and d.thickness_variance > 0: + v = d.thickness / 100 * d.thickness_variance + z = uniform(self.top, self.top + v) + else: + z = self.top - y += bl + s + p = len(verts) + verts.extend([(x, y, z), (x1, y, z), (x1, y1, z), (x, y1, z)]) + faces.append([p + 3, p + 2, p + 1, p]) + if rotate_uv: + uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)]) + else: + uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)]) + self.add_matid(d, matids) + + def add_face(self, d, verts, faces, matids, uvs, p0, p1, p2, p3): + """ + Adds vertices and faces for a place, clip to outer boundaries if clip is True + :param x: start x position + :param y: start y position + :param w: width (in x direction) + :param l: length (in y direction) + """ + + if d.vary_thickness and d.thickness_variance > 0: + v = d.thickness / 100 * d.thickness_variance + z = uniform(self.top, self.top + v) + else: + z = self.top + + p = len(verts) + verts.extend([(v[0], v[1], z) for v in [p0, p1, p2, p3]]) + faces.append([p + 3, p + 2, p + 1, p]) + uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)]) + self.add_matid(d, matids) + + def add_manipulator(self, name, pt1, pt2, pt3): + m = self.manipulators.add() + m.prop1_name = name + m.set_pts([pt1, pt2, pt3]) + + def generate_pattern(self, d, verts, faces, matids, uvs): + # clear data before refreshing it + + self.uv_factor = 1 / max(self.xmax, self.ymax) # automatically scale to keep within reasonable bounds + + if d.pattern == "boards": + self.boards(d, verts, faces, matids, uvs) + elif d.pattern == "square_parquet": + self.square_parquet(d, verts, faces, matids, uvs) + elif d.pattern == "herringbone": + self.herringbone(d, verts, faces, matids, uvs) + elif d.pattern == "herringbone_parquet": + self.herringbone_parquet(d, verts, faces, matids, uvs) + elif d.pattern == "regular_tile": + self.regular_tile(d, verts, faces, matids, uvs) + elif d.pattern == "hopscotch": + self.hopscotch(d, verts, faces, matids, uvs) + elif d.pattern == "stepping_stone": + self.stepping_stone(d, verts, faces, matids, uvs) + elif d.pattern == "hexagon": + self.hexagon(d, verts, faces, matids, uvs) + elif d.pattern == "windmill": + self.windmill(d, verts, faces, matids, uvs) - orient_length = not orient_length - start_orient_length = not start_orient_length +def update(self, context): + self.update(context) - x += bl + s - return verts, faces +def update_type(self, context): + d = self.find_in_selection(context) -def wood_regular(ow, ol, bw, bl, s_l, s_w, - is_length_vary, length_vary, - is_width_vary, width_vary, - is_offset, offset, - is_ran_offset, offset_vary, - max_boards, is_r_h, - r_h, th): - verts = [] - faces = [] - x = 0.0 - row = 0 - while x < ow: + if d is not None and d.auto_update: - if is_width_vary: - v = bw * (width_vary / 100) * 0.499 - bw2 = uniform(bw / 2 - v, bw / 2 + v) - else: - bw2 = bw + d.auto_update = False + # find part index + idx = 0 + for i, part in enumerate(d.parts): + if part == self: + idx = i + break - x1 = min(x + bw2, ow) - if is_offset: - if is_ran_offset: - v = bl * (offset_vary / 100) * 0.5 - y = -uniform(bl / 2 - v, bl / 2 + v) + part = d.parts[idx] + a0 = 0 + if idx > 0: + g = d.get_generator() + w0 = g.segs[idx - 1] + a0 = w0.straight(1).angle + if "C_" in self.type: + w = w0.straight_floor(part.a0, part.length) else: - y = -(row % 2) * bl * (offset / 100) + w = w0.curved_floor(part.a0, part.da, part.radius) + else: + g = FloorGenerator(None) + g.add_part(self) + w = g.segs[0] + + # w0 - w - w1 + dp = w.p1 - w.p0 + if "C_" in self.type: + part.radius = 0.5 * dp.length + part.da = pi + a0 = atan2(dp.y, dp.x) - pi / 2 - a0 else: - y = 0 + part.length = dp.length + a0 = atan2(dp.y, dp.x) - a0 + + if a0 > pi: + a0 -= 2 * pi + if a0 < -pi: + a0 += 2 * pi + part.a0 = a0 + + if idx + 1 < d.n_parts: + # adjust rotation of next part + part1 = d.parts[idx + 1] + if "C_" in part.type: + a0 = part1.a0 - pi / 2 + else: + a0 = part1.a0 + w.straight(1).angle - atan2(dp.y, dp.x) - row += 1 - counter = 1 + if a0 > pi: + a0 -= 2 * pi + if a0 < -pi: + a0 += 2 * pi + part1.a0 = a0 - while y < ol: + d.auto_update = True - z = th - if is_r_h: - v = z * 0.5 * (r_h / 100) - z = uniform(z / 2 - v, z / 2 + v) +def update_manipulators(self, context): + self.update(context, manipulable_refresh=True) - bl2 = bl - if is_length_vary: - if (counter >= max_boards): - bl2 = ol - else: - v = bl * (length_vary / 100) * 0.5 - bl2 = uniform(bl / 2 - v, bl / 2 + v) +def update_path(self, context): + self.update_path(context) - y0 = max(0, y) - y1 = min(y + bl2, ol) - if y1 > y0: - p = len(verts) +class archipack_floor_part(PropertyGroup): - verts.extend([(x, y0, z), (x1, y0, z), (x1, y1, z), (x, y1, z)]) - faces.append((p, p + 1, p + 2, p + 3)) + """ + A single manipulable polyline like segment + polyline like segment line or arc based + """ + type = EnumProperty( + items=( + ('S_SEG', 'Straight', '', 0), + ('C_SEG', 'Curved', '', 1), + ), + default='S_SEG', + update=update_type + ) + length = FloatProperty( + name="length", + min=0.01, + default=2.0, + update=update + ) + radius = FloatProperty( + name="radius", + min=0.5, + default=0.7, + update=update + ) + da = FloatProperty( + name="angle", + min=-pi, + max=pi, + default=pi / 2, + subtype='ANGLE', unit='ROTATION', + update=update + ) + a0 = FloatProperty( + name="start angle", + min=-2 * pi, + max=2 * pi, + default=0, + subtype='ANGLE', unit='ROTATION', + update=update + ) + offset = FloatProperty( + name="Offset", + description="Add to current segment offset", + default=0, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + manipulators = CollectionProperty(type=archipack_manipulator) + + def find_in_selection(self, context): + """ + find witch selected object this instance belongs to + provide support for "copy to selected" + """ + selected = [o for o in context.selected_objects] + for o in selected: + props = archipack_floor.datablock(o) + if props: + for part in props.parts: + if part == self: + return props + return None + + def update(self, context, manipulable_refresh=False): + props = self.find_in_selection(context) + if props is not None: + props.update(context, manipulable_refresh) + + def draw(self, context, layout, index): + box = layout.box() + box.prop(self, "type", text=str(index + 1)) - y += bl2 + s_l + if self.type in ['C_SEG']: + box.prop(self, "radius") + box.prop(self, "da") + else: + box.prop(self, "length") + box.prop(self, "a0") - counter += 1 - x += bw2 + s_w +class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): + n_parts = IntProperty( + name="parts", + min=1, + default=1, update=update_manipulators + ) + parts = CollectionProperty(type=archipack_floor_part) + user_defined_path = StringProperty( + name="user defined", + update=update_path + ) + user_defined_resolution = IntProperty( + name="resolution", + min=1, + max=128, + default=12, update=update_path + ) + closed = BoolProperty( + default=True, + name="Close", + options={'SKIP_SAVE'}, + update=update_manipulators + ) + # UI layout related + parts_expand = BoolProperty( + options={'SKIP_SAVE'}, + default=False + ) - return verts, faces + pattern = EnumProperty( + name='Floor Pattern', items=(("boards", "Boards", ""), ("square_parquet", "Square Parquet", ""), + ("herringbone_parquet", "Herringbone Parquet", ""), + ("herringbone", "Herringbone", ""), ("regular_tile", "Regular Tile", ""), + ("hopscotch", "Hopscotch", ""), ("stepping_stone", "Stepping Stone", ""), + ("hexagon", "Hexagon", ""), ("windmill", "Windmill", "")), + default="boards", update=update + ) + spacing = FloatProperty( + name='Spacing', + description='The amount of space between boards or tiles in both directions', + unit='LENGTH', subtype='DISTANCE', + min=0, + default=0.005, + precision=2, + update=update + ) + thickness = FloatProperty( + name='Thickness', + description='Thickness', + unit='LENGTH', subtype='DISTANCE', + min=0.0, + default=0.005, + precision=2, + update=update + ) + vary_thickness = BoolProperty( + name='Vary Thickness', + description='Vary board thickness?', + default=False, + update=update + ) + thickness_variance = FloatProperty( + name='Thickness Variance', + description='How much board thickness can vary by', + min=0, max=100, + default=25, + precision=2, + subtype='PERCENTAGE', + update=update + ) + board_width = FloatProperty( + name='Board Width', + description='The width of the boards', + unit='LENGTH', subtype='DISTANCE', + min=0.02, + default=0.2, + precision=2, + update=update + ) + vary_width = BoolProperty( + name='Vary Width', + description='Vary board width', + default=False, + update=update + ) + width_variance = FloatProperty( + name='Width Variance', + description='How much board width can vary by', + subtype='PERCENTAGE', + min=1, max=100, default=50, + precision=2, + update=update + ) + width_spacing = FloatProperty( + name='Width Spacing', + description='The amount of space between boards in the width direction', + unit='LENGTH', subtype='DISTANCE', + min=0, + default=0.002, + precision=2, + update=update + ) -def tile_grout(ow, ol, depth, th): - z = min(th - 0.001, max(0.001, th - depth)) - x = ow - y = ol + board_length = FloatProperty( + name='Board Length', + description='The length of the boards', + unit='LENGTH', subtype='DISTANCE', + precision=2, + min=0.02, + default=2, + update=update + ) + short_board_length = FloatProperty( + name='Board Length', + description='The length of the boards', + unit='LENGTH', subtype='DISTANCE', + precision=2, + min=0.02, + default=2, + update=update + ) + vary_length = BoolProperty( + name='Vary Length', + description='Vary board length', + default=False, + update=update + ) + length_variance = FloatProperty( + name='Length Variance', + description='How much board length can vary by', + subtype='PERCENTAGE', + min=1, max=100, default=50, + precision=2, update=update + ) + max_boards = IntProperty( + name='Max Boards', + description='Max number of boards in one row', + min=1, + default=20, + update=update + ) + length_spacing = FloatProperty( + name='Length Spacing', + description='The amount of space between boards in the length direction', + unit='LENGTH', subtype='DISTANCE', + min=0, + default=0.002, + precision=2, + update=update + ) - verts = [(0.0, 0.0, 0.0), (0.0, 0.0, z), (x, 0.0, z), (x, 0.0, 0.0), - (0.0, y, 0.0), (0.0, y, z), (x, y, z), (x, y, 0.0)] + # parquet specific + boards_in_group = IntProperty( + name='Boards in Group', + description='Number of boards in a group', + min=1, default=4, + update=update + ) - faces = [(0, 3, 2, 1), (4, 5, 6, 7), (0, 1, 5, 4), - (1, 2, 6, 5), (3, 7, 6, 2), (0, 4, 7, 3)] + # tile specific + tile_width = FloatProperty( + name='Tile Width', + description='Width of the tiles', + unit='LENGTH', subtype='DISTANCE', + min=0.002, + default=0.2, + precision=2, + update=update + ) + tile_length = FloatProperty( + name='Tile Length', + description='Length of the tiles', + unit='LENGTH', subtype='DISTANCE', + precision=2, + min=0.02, + default=0.3, + update=update + ) - return verts, faces + # grout + add_grout = BoolProperty( + name='Add Grout', + description='Add grout', + default=False, + update=update + ) + mortar_depth = FloatProperty( + name='Mortar Depth', + description='The depth of the mortar from the surface of the tile', + unit='LENGTH', subtype='DISTANCE', + precision=2, + step=0.005, + min=0, + default=0.001, + update=update + ) + # regular tile + random_offset = BoolProperty( + name='Random Offset', + description='Random amount of offset for each row of tiles', + update=update, default=False + ) + offset = FloatProperty( + name='Offset', + description='How much to offset each row of tiles', + min=0, max=100, default=0, + precision=2, + update=update + ) + offset_variance = FloatProperty( + name='Offset Variance', + description='How much to vary the offset each row of tiles', + min=0.001, max=100, default=50, + precision=2, + update=update + ) -def update(self, context): - self.update(context) + # UV stuff + random_uvs = BoolProperty( + name='Random UV\'s', update=update, default=True, description='Random UV positions for the faces' + ) + # bevel + bevel = BoolProperty( + name='Bevel', update=update, default=False, description='Bevel upper faces' + ) + bevel_amount = FloatProperty( + name='Bevel Amount', + description='Bevel amount', + unit='LENGTH', subtype='DISTANCE', + min=0.0001, default=0.001, + precision=2, step=0.05, + update=update + ) -class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): - tile_types = EnumProperty( - items=( - ("1", "Tiles", ""), - ("2", "Large + Small", ""), - ("3", "Large + Many Small", ""), - ("4", "Hexagonal", ""), - ("21", "Planks", ""), - ("22", "Parquet", ""), - ("23", "Herringbone Parquet", ""), - ("24", "Herringbone", "") - ), - default="1", - description="Tile Type", - update=update, - name="") - b_length_s = FloatProperty( - name="Board Length", - min=0.01, - default=2.0, - unit='LENGTH', subtype='DISTANCE', - description="Board Length", - update=update) - hb_direction = EnumProperty( - items=( - ("1", "Forwards (+y)", ""), - ("2", "Backwards (-y)", ""), - ("3", "Right (+x)", ""), - ("4", "Left (-x)", "") - ), - name="Direction", - description="Herringbone Direction", - update=update) - thickness = FloatProperty( - name="Floor Thickness", - min=0.01, - default=0.1, - unit='LENGTH', subtype='DISTANCE', - description="Thickness Of Flooring", - update=update) - num_boards = IntProperty( - name="# Of Boards", - min=2, - max=6, - default=4, - description="Number Of Boards In Square", - update=update) - space_l = FloatProperty( - name="Length Spacing", - min=0.001, - default=0.005, - step=0.01, - unit='LENGTH', subtype='DISTANCE', - description="Space Between Boards Length Ways", - update=update) - space_w = FloatProperty( - name="Width Spacing", - min=0.001, - default=0.005, - step=0.01, - unit='LENGTH', subtype='DISTANCE', - description="Space Between Boards Width Ways", - update=update) - spacing = FloatProperty( - name="Spacing", - min=0.001, - default=0.005, - step=0.01, - unit='LENGTH', subtype='DISTANCE', - description="Space Between Tiles/Boards", - update=update) - is_bevel = BoolProperty( - name="Bevel?", - default=False, - update=update) - bevel_res = IntProperty( - name="Bevel Resolution", - min=1, - max=10, - default=1, - update=update) - bevel_amo = FloatProperty( - name="Bevel Amount", - min=0.001, - default=0.0015, - step=0.01, - unit='LENGTH', subtype='DISTANCE', - description="Bevel Amount", - update=update) - is_ran_thickness = BoolProperty( - name="Random Thickness?", - default=False, - update=update) - ran_thickness = FloatProperty( - name="Thickness Variance", - min=0.1, - max=100.0, - default=50.0, - subtype="PERCENTAGE", - update=update) - is_floor_bottom = BoolProperty( - name="Floor bottom", - default=True, - update=update) - t_width = FloatProperty( - name="Tile Width", - min=0.01, - default=0.3, - unit='LENGTH', subtype='DISTANCE', - description="Tile Width", - update=update) - t_length = FloatProperty( - name="Tile Length", - min=0.01, - default=0.3, - unit='LENGTH', subtype='DISTANCE', - description="Tile Length", - update=update) - is_grout = BoolProperty( - name="Grout", - default=False, - description="Enable grout", - update=update) - grout_depth = FloatProperty( - name="Grout Depth", - min=0.001, - default=0.005, - step=0.01, - unit='LENGTH', subtype='DISTANCE', - description="Grout Depth", - update=update) - is_offset = BoolProperty( - name="Offset ?", - default=False, - description="Offset Rows", - update=update) - offset = FloatProperty( - name="Offset", - min=0.001, - max=100.0, - default=50.0, - subtype="PERCENTAGE", - description="Tile Offset Amount", - update=update) - is_random_offset = BoolProperty( - name="Random Offset?", - default=False, - description="Offset Tile Rows Randomly", - update=update) - offset_vary = FloatProperty( - name="Offset Variance", - min=0.001, - max=100.0, - default=50.0, - subtype="PERCENTAGE", - description="Offset Variance", - update=update) - t_width_s = FloatProperty( - name="Small Tile Width", - min=0.02, - default=0.2, - unit='LENGTH', subtype='DISTANCE', - description="Small Tile Width", - update=update) - over_width = FloatProperty( - name="Overall Width", - min=0.02, - default=4, - unit='LENGTH', subtype='DISTANCE', - description="Overall Width", - update=update) - over_length = FloatProperty( - name="Overall Length", - min=0.02, - default=4, - unit='LENGTH', subtype='DISTANCE', - description="Overall Length", - update=update) - b_width = FloatProperty( - name="Board Width", - min=0.01, - default=0.2, - unit='LENGTH', subtype='DISTANCE', - description="Board Width", - update=update) - b_length = FloatProperty( - name="Board Length", - min=0.01, - default=0.8, - unit='LENGTH', subtype='DISTANCE', - description="Board Length", - update=update) - is_length_vary = BoolProperty( - name="Vary Length?", - default=False, - description="Vary Lengths?", - update=update) - length_vary = FloatProperty( - name="Length Variance", - min=1.00, - max=100.0, - default=50.0, - subtype="PERCENTAGE", - description="Length Variance", - update=update) - max_boards = IntProperty( - name="Max # Of Boards", - min=2, - default=2, - description="Maximum Number Of Boards Possible In One Length", - update=update) - is_width_vary = BoolProperty( - name="Vary Width?", - default=False, - description="Vary Widths?", - update=update) - width_vary = FloatProperty( - name="Width Variance", - min=1.00, - max=100.0, - default=50.0, - subtype="PERCENTAGE", - description="Width Variance", - update=update) - is_mat_vary = BoolProperty( + vary_materials = BoolProperty( name="Vary Material?", default=False, description="Vary Material indexes", update=update) - mat_vary = IntProperty( + matid = IntProperty( name="#variations", min=1, max=10, @@ -841,127 +1236,373 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): description="Material index maxi", update=update) auto_update = BoolProperty( - options={'SKIP_SAVE'}, - default=True, - update=update + options={'SKIP_SAVE'}, + default=True, + update=update_manipulators + ) + z = FloatProperty( + name="dumb z", + description="Dumb z for manipulator placeholder", + default=0.01, + options={'SKIP_SAVE'} ) + def get_generator(self): + g = FloorGenerator(self.parts) + for part in self.parts: + # type, radius, da, length + g.add_part(part) + + g.set_offset() + + g.close(self.closed) + g.locate_manipulators() + return g + + def update_parts(self, o): + + for i in range(len(self.parts), self.n_parts, -1): + self.parts.remove(i - 1) + + # add rows + for i in range(len(self.parts), self.n_parts): + self.parts.add() + + self.setup_manipulators() + + g = self.get_generator() + + return g + + @staticmethod + def create_uv_seams(bm): + handled = set() + for edge in bm.edges: + if edge.verts[0].co.z == 0 and edge.verts[1].co.z == 0: # bottom + # make sure both vertices on the edge haven't been handled, this forces one edge to not be made a seam + # leaving the bottom face still attached + if not (edge.verts[0].index in handled and edge.verts[1].index in handled): + edge.seam = True + handled.add(edge.verts[0].index) + handled.add(edge.verts[1].index) + elif edge.verts[0].co.z != edge.verts[1].co.z: # not horizontal, so they are vertical seams + edge.seam = True + + def is_cw(self, pts): + p0 = pts[0] + d = 0 + for p in pts[1:]: + d += (p.x * p0.y - p.y * p0.x) + p0 = p + return d > 0 + + def interpolate_bezier(self, pts, wM, p0, p1, resolution): + # straight segment, worth testing here + # since this can lower points count by a resolution factor + # use normalized to handle non linear t + if resolution == 0: + pts.append(wM * p0.co.to_3d()) + else: + v = (p1.co - p0.co).normalized() + d1 = (p0.handle_right - p0.co).normalized() + d2 = (p1.co - p1.handle_left).normalized() + if d1 == v and d2 == v: + pts.append(wM * p0.co.to_3d()) + else: + seg = interpolate_bezier(wM * p0.co, + wM * p0.handle_right, + wM * p1.handle_left, + wM * p1.co, + resolution + 1) + for i in range(resolution): + pts.append(seg[i].to_3d()) + + def from_spline(self, context, wM, resolution, spline): + pts = [] + if spline.type == 'POLY': + pts = [wM * p.co.to_3d() for p in spline.points] + if spline.use_cyclic_u: + pts.append(pts[0]) + elif spline.type == 'BEZIER': + points = spline.bezier_points + for i in range(1, len(points)): + p0 = points[i - 1] + p1 = points[i] + self.interpolate_bezier(pts, wM, p0, p1, resolution) + if spline.use_cyclic_u: + p0 = points[-1] + p1 = points[0] + self.interpolate_bezier(pts, wM, p0, p1, resolution) + pts.append(pts[0]) + else: + pts.append(wM * points[-1].co) + + pt = wM.inverted() * pts[0] + + # pretranslate + o = self.find_in_selection(context, self.auto_update) + o.matrix_world = wM * Matrix([ + [1, 0, 0, pt.x], + [0, 1, 0, pt.y], + [0, 0, 1, pt.z], + [0, 0, 0, 1] + ]) + self.from_points(pts) + + def from_points(self, pts): + + if self.is_cw(pts): + pts = list(reversed(pts)) + + self.auto_update = False + + self.n_parts = len(pts) - 1 + + self.update_parts(None) + + p0 = pts.pop(0) + a0 = 0 + for i, p1 in enumerate(pts): + dp = p1 - p0 + da = atan2(dp.y, dp.x) - a0 + if da > pi: + da -= 2 * pi + if da < -pi: + da += 2 * pi + if i >= len(self.parts): + break + p = self.parts[i] + p.length = dp.to_2d().length + p.dz = dp.z + p.a0 = da + a0 += da + p0 = p1 + + self.closed = True + self.auto_update = True + + def update_path(self, context): + user_def_path = context.scene.objects.get(self.user_defined_path) + if user_def_path is not None and user_def_path.type == 'CURVE': + self.from_spline( + context, + user_def_path.matrix_world, + self.user_defined_resolution, + user_def_path.data.splines[0]) + + def add_manipulator(self, name, pt1, pt2, pt3): + m = self.manipulators.add() + m.prop1_name = name + m.set_pts([pt1, pt2, pt3]) + + def update_manipulators(self): + self.manipulators.clear() # clear every time, add new ones + self.add_manipulator("length", (0, 0, 0), (0, self.length, 0), (-0.4, 0, 0)) + self.add_manipulator("width", (0, 0, 0), (self.width, 0, 0), (0.4, 0, 0)) + + z = self.thickness + + if self.pattern == "boards": + self.add_manipulator("board_length", (0, 0, z), (0, self.board_length, z), (0.1, 0, z)) + self.add_manipulator("board_width", (0, 0, z), (self.board_width, 0, z), (-0.2, 0, z)) + elif self.pattern == "square_parquet": + self.add_manipulator("short_board_length", (0, 0, z), (0, self.short_board_length, z), (-0.2, 0, z)) + elif self.pattern in ("herringbone", "herringbone_parquet"): + dia = self.short_board_length * cos(radians(45)) + dia2 = self.board_width * cos(radians(45)) + self.add_manipulator("short_board_length", (0, 0, z), (dia, dia, z), (0, 0, z)) + self.add_manipulator("board_width", (dia, 0, z), (dia - dia2, dia2, z), (0, 0, z)) + else: + tl = self.tile_length + tw = self.tile_width + + if self.pattern in ("regular_tile", "hopscotch", "stepping_stone"): + self.add_manipulator("tile_width", (0, tl, z), (tw, tl, z), (0, 0, z)) + self.add_manipulator("tile_length", (0, 0, z), (0, tl, z), (0, 0, z)) + elif self.pattern == "hexagon": + self.add_manipulator("tile_width", (tw / 2 + self.spacing, 0, z), (tw * 1.5 + self.spacing, 0, z), + (0, 0, 0)) + elif self.pattern == "windmill": + self.add_manipulator("tile_width", (0, 0, z), (tw, 0, 0), (0, 0, z)) + self.add_manipulator("tile_length", (0, tl / 2 + self.spacing, z), (0, tl * 1.5 + self.spacing, z), + (0, 0, z)) + def setup_manipulators(self): - if len(self.manipulators) < 1: - # add manipulator for x property - s = self.manipulators.add() - s.prop1_name = "over_width" - # s.prop2_name = "x" - s.type_key = 'SIZE' - # add manipulator for y property + if len(self.manipulators) < 1: s = self.manipulators.add() - s.prop1_name = "over_length" - # s.prop2_name = "y" - s.type_key = 'SIZE' - - def update(self, context): + s.type_key = "SIZE" + s.prop1_name = "z" + s.normal = Vector((0, 1, 0)) + + for i in range(self.n_parts): + p = self.parts[i] + n_manips = len(p.manipulators) + if n_manips < 1: + s = p.manipulators.add() + s.type_key = "ANGLE" + s.prop1_name = "a0" + p.manipulators[0].type_key = 'ANGLE' + if n_manips < 2: + s = p.manipulators.add() + s.type_key = "SIZE" + s.prop1_name = "length" + if n_manips < 3: + s = p.manipulators.add() + s.type_key = 'WALL_SNAP' + s.prop1_name = str(i) + s.prop2_name = 'z' + if n_manips < 4: + s = p.manipulators.add() + s.type_key = 'DUMB_STRING' + s.prop1_name = str(i + 1) + p.manipulators[2].prop1_name = str(i) + p.manipulators[3].prop1_name = str(i + 1) + + self.parts[-1].manipulators[0].type_key = 'DUMB_ANGLE' + + def update(self, context, manipulable_refresh=False): o = self.find_in_selection(context, self.auto_update) if o is None: return - self.setup_manipulators() + # clean up manipulators before any data model change + if manipulable_refresh: + self.manipulable_disable(context) - verts, faces = create_flooring(self.tile_types, self.over_width, - self.over_length, self.b_width, self.b_length, self.b_length_s, - self.is_length_vary, self.length_vary, self.num_boards, self.space_l, - self.space_w, self.spacing, self.t_width, self.t_length, self.is_offset, - self.offset, self.is_random_offset, self.offset_vary, self.t_width_s, - self.is_width_vary, self.width_vary, self.max_boards, self.is_ran_thickness, - self.ran_thickness, self.thickness, self.hb_direction) - - if self.is_mat_vary: - # hexagon made of 2 faces - if self.tile_types == '4': - matids = [] - for i in range(int(len(faces) / 2)): - id = randint(1, self.mat_vary) - matids.extend([id, id]) - else: - matids = [randint(1, self.mat_vary) for i in faces] - else: - matids = [1 for i in faces] - - uvs = [[(0, 0), (0, 1), (1, 1), (1, 0)] for i in faces] - - bmed.buildmesh(context, - o, - verts, - faces, - matids=matids, - uvs=uvs, - weld=False, - auto_smooth=False) - - # cut hexa and herringbone wood - # disable when boolean modifier is found - enable_bissect = True - for m in o.modifiers: - if m.type == 'BOOLEAN': - enable_bissect = False - - if enable_bissect and self.tile_types in ('4', '23', '24'): - bmed.bissect(context, o, Vector((0, 0, 0)), Vector((0, -1, 0))) - # Up - bmed.bissect(context, o, Vector((0, self.over_length, 0)), Vector((0, 1, 0))) - # left - bmed.bissect(context, o, Vector((0, 0, 0)), Vector((-1, 0, 0))) - # right - bmed.bissect(context, o, Vector((self.over_width, 0, 0)), Vector((1, 0, 0))) - - if self.is_bevel: - bevel = self.bevel_amo - else: - bevel = 0 + g = self.update_parts(o) - if self.is_grout: - th = min(self.grout_depth + bevel, self.thickness - 0.001) - bottom = th - else: - th = self.thickness - bottom = 0 + g.cut(context, o) + g.floor(context, o, self) - bmed.solidify(context, - o, - th, - floor_bottom=( - self.is_floor_bottom and - self.is_ran_thickness and - self.tile_types in ('21') - ), - altitude=bottom) - - # bevel mesh - if self.is_bevel: - bmed.bevel(context, o, self.bevel_amo, segments=self.bevel_res) - - # create grout - if self.is_grout: - verts, faces = tile_grout(self.over_width, self.over_length, self.grout_depth, self.thickness) - matids = [0 for i in faces] - uvs = [[(0, 0), (0, 1), (1, 1), (1, 0)] for i in faces] - bmed.addmesh(context, - o, - verts, - faces, - matids=matids, - uvs=uvs, - weld=False, - auto_smooth=False) - - x, y = self.over_width, self.over_length - self.manipulators[0].set_pts([(0, 0, 0), (x, 0, 0), (1, 0, 0)]) - self.manipulators[1].set_pts([(0, 0, 0), (0, y, 0), (-1, 0, 0)]) + # enable manipulators rebuild + if manipulable_refresh: + self.manipulable_refresh = True + # restore context self.restore_context(context) + def manipulable_setup(self, context): + """ + NOTE: + this one assume context.active_object is the instance this + data belongs to, failing to do so will result in wrong + manipulators set on active object + """ + self.manipulable_disable(context) + + o = context.active_object + + self.setup_manipulators() + + for i, part in enumerate(self.parts): + if i >= self.n_parts: + break + + if i > 0: + # start angle + self.manip_stack.append(part.manipulators[0].setup(context, o, part)) + + # length / radius + angle + self.manip_stack.append(part.manipulators[1].setup(context, o, part)) + + # snap point + self.manip_stack.append(part.manipulators[2].setup(context, o, self)) + # index + self.manip_stack.append(part.manipulators[3].setup(context, o, self)) + + for m in self.manipulators: + self.manip_stack.append(m.setup(context, o, self)) + + def manipulable_invoke(self, context): + """ + call this in operator invoke() + """ + # print("manipulable_invoke") + if self.manipulate_mode: + self.manipulable_disable(context) + return False + + self.manipulable_setup(context) + self.manipulate_mode = True + + self._manipulable_invoke(context) + + return True + + +def update_hole(self, context): + self.update(context, update_parent=True) + + +def update_operation(self, context): + self.reverse(context, make_ccw=(self.operation == 'INTERSECTION')) + + +class archipack_floor_cutter_segment(ArchipackCutterPart, PropertyGroup): + manipulators = CollectionProperty(type=archipack_manipulator) + type = EnumProperty( + name="Type", + items=( + ('DEFAULT', 'Side', 'Side with rake', 0), + ('BOTTOM', 'Bottom', 'Bottom with gutter', 1), + ('LINK', 'Side link', 'Side witout decoration', 2), + ('AXIS', 'Top', 'Top part with hip and beam', 3) + # ('LINK_VALLEY', 'Side valley', 'Side with valley', 3), + # ('LINK_HIP', 'Side hip', 'Side with hip', 4) + ), + default='DEFAULT', + update=update_hole + ) + + def find_in_selection(self, context): + selected = [o for o in context.selected_objects] + for o in selected: + d = archipack_floor_cutter.datablock(o) + if d: + for part in d.parts: + if part == self: + return d + return None + + def draw(self, layout, context, index): + box = layout.box() + box.label(text="Part:" + str(index + 1)) + # box.prop(self, "type", text=str(index + 1)) + box.prop(self, "length") + box.prop(self, "a0") + + +class archipack_floor_cutter(ArchipackCutter, ArchipackObject, Manipulable, PropertyGroup): + parts = CollectionProperty(type=archipack_floor_cutter_segment) + + def update_points(self, context, o, pts, update_parent=False): + """ + Create boundary from roof + """ + self.auto_update = False + self.from_points(pts) + self.auto_update = True + if update_parent: + self.update_parent(context, o) + + def update_parent(self, context, o): + + d = archipack_floor.datablock(o.parent) + if d is not None: + o.parent.select = True + context.scene.objects.active = o.parent + d.update(context) + o.parent.select = False + context.scene.objects.active = o + + +# ------------------------------------------------------------------ +# Define panel class to show object parameters in ui panel (N) +# ------------------------------------------------------------------ + class ARCHIPACK_PT_floor(Panel): bl_idname = "ARCHIPACK_PT_floor" @@ -980,13 +1621,12 @@ class ARCHIPACK_PT_floor(Panel): if not archipack_floor.filter(o): return layout = self.layout - + scene = context.scene # retrieve datablock of your object props = archipack_floor.datablock(o) - - # Manipulate mode operator - layout.operator('archipack.floor_manipulate', icon='HAND') - + # manipulate + layout.operator("archipack.floor_manipulate", icon="HAND") + layout.separator() box = layout.box() row = box.row(align=True) @@ -1000,124 +1640,170 @@ class ARCHIPACK_PT_floor(Panel): text="", icon='ZOOMOUT').remove_active = True - layout.prop(props, "tile_types", icon="OBJECT_DATA") + box = layout.box() + box.operator('archipack.floor_cutter').parent = o.name - layout.separator() + box = layout.box() + box.label(text="From curve") + box.prop_search(props, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE') + if props.user_defined_path != "": + box.prop(props, 'user_defined_resolution') - layout.prop(props, "over_width") - layout.prop(props, "over_length") + box = layout.box() + row = box.row() + if props.parts_expand: + row.prop(props, 'parts_expand', icon="TRIA_DOWN", icon_only=True, text="Parts", emboss=False) + box.prop(props, 'n_parts') + # box.prop(prop, 'closed') + for i, part in enumerate(props.parts): + part.draw(context, layout, i) + else: + row.prop(props, 'parts_expand', icon="TRIA_RIGHT", icon_only=True, text="Parts", emboss=False) layout.separator() + box = layout.box() + box.prop(props, 'pattern', text="") + # thickness + box.separator() + box.prop(props, 'thickness') + box.prop(props, 'vary_thickness', icon='RNDCURVE') + if props.vary_thickness: + box.prop(props, 'thickness_variance') + + box.separator() + if props.pattern == 'boards': + box.prop(props, 'board_length') + box.prop(props, 'vary_length', icon='RNDCURVE') + if props.vary_length: + box.prop(props, 'length_variance') + box.prop(props, 'max_boards') + box.separator() + + # width + box.prop(props, 'board_width') + # vary width + box.prop(props, 'vary_width', icon='RNDCURVE') + if props.vary_width: + box.prop(props, 'width_variance') + box.separator() + box.prop(props, 'length_spacing') + box.prop(props, 'width_spacing') + + elif props.pattern in {'square_parquet', 'herringbone_parquet', 'herringbone'}: + box.prop(props, 'short_board_length') + + if props.pattern != "square_parquet": + box.prop(props, "board_width") + box.prop(props, "spacing") + + if props.pattern == 'square_parquet': + box.prop(props, 'boards_in_group') + elif props.pattern in {'regular_tile', 'hopscotch', 'stepping_stone', 'hexagon', 'windmill'}: + # width and length and mortar + if props.pattern != "hexagon": + box.prop(props, "tile_length") + box.prop(props, "tile_width") + box.prop(props, "spacing") + + if props.pattern in {"regular_tile", "boards"}: + box.separator() + box.prop(props, "random_offset", icon="RNDCURVE") + if props.random_offset: + box.prop(props, "offset_variance") + else: + box.prop(props, "offset") - # width and lengths - layout.prop(props, "thickness") + # grout + box.separator() + box.prop(props, 'add_grout', icon='MESH_GRID') + if props.add_grout: + box.prop(props, 'mortar_depth') - type = int(props.tile_types) + # bevel + box.separator() + box.prop(props, 'bevel', icon='MOD_BEVEL') + if props.bevel: + box.prop(props, 'bevel_amount') - if type > 20: - # Wood types - layout.prop(props, "b_width") - else: - # Tiles types - if type != 4: - # Not hexagonal - layout.prop(props, "t_width") - layout.prop(props, "t_length") - else: - layout.prop(props, "t_width_s") - - # Herringbone - if type in (23, 24): - layout.prop(props, "b_length_s") - layout.prop(props, "hb_direction") - - # Parquet - if type == 22: - layout.prop(props, "num_boards") - - # Planks - if type == 21: - layout.prop(props, "b_length") - layout.prop(props, "space_w") - layout.prop(props, "space_l") - - layout.separator() - layout.prop(props, "is_length_vary", icon="NLA") - if props.is_length_vary: - layout.prop(props, "length_vary") - layout.prop(props, "max_boards") - - layout.separator() - layout.prop(props, "is_width_vary", icon="UV_ISLANDSEL") - if props.is_width_vary: - layout.prop(props, "width_vary") - - layout.separator() - layout.prop(props, "is_ran_thickness", icon="RNDCURVE") - if props.is_ran_thickness: - layout.prop(props, "ran_thickness") - layout.prop(props, "is_floor_bottom", icon="MOVE_DOWN_VEC") - else: - layout.prop(props, "spacing") - - # Planks and tiles - if type in (1, 21): - layout.separator() - layout.prop(props, "is_offset", icon="OOPS") - if props.is_offset: - layout.prop(props, "is_random_offset", icon="NLA") - if not props.is_random_offset: - layout.prop(props, "offset") - else: - layout.prop(props, "offset_vary") + box.separator() + box.prop(props, "vary_materials", icon="MATERIAL") + if props.vary_materials: + box.prop(props, "matid") - # bevel - layout.separator() - layout.prop(props, "is_bevel", icon="MOD_BEVEL") - if props.is_bevel: - layout.prop(props, "bevel_res", icon="OUTLINER_DATA_CURVE") - layout.prop(props, "bevel_amo") - # Grout - layout.separator() - layout.prop(props, "is_grout", icon="OBJECT_DATA") - if props.is_grout: - layout.prop(props, "grout_depth") +class ARCHIPACK_PT_floor_cutter(Panel): + bl_idname = "ARCHIPACK_PT_floor_cutter" + bl_label = "Floor Cutter" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'ArchiPack' - layout.separator() - layout.prop(props, "is_mat_vary", icon="MATERIAL") - if props.is_mat_vary: - layout.prop(props, "mat_vary") + @classmethod + def poll(cls, context): + return archipack_floor_cutter.filter(context.active_object) + + def draw(self, context): + prop = archipack_floor_cutter.datablock(context.active_object) + if prop is None: + return + layout = self.layout + scene = context.scene + box = layout.box() + box.operator('archipack.floor_cutter_manipulate', icon='HAND') + box.prop(prop, 'operation', text="") + box = layout.box() + box.label(text="From curve") + box.prop_search(prop, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE') + if prop.user_defined_path != "": + box.prop(prop, 'user_defined_resolution') + # box.prop(prop, 'x_offset') + # box.prop(prop, 'angle_limit') + """ + box.prop_search(prop, "boundary", scene, "objects", text="", icon='OUTLINER_OB_CURVE') + """ + prop.draw(layout, context) + + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ class ARCHIPACK_OT_floor(ArchipackCreateTool, Operator): bl_idname = "archipack.floor" bl_label = "Floor" - bl_description = "Create Floor" + bl_description = "Floor" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} def create(self, context): - - # Create an empty mesh datablock + """ + expose only basic params in operator + use object property for other params + """ m = bpy.data.meshes.new("Floor") - - # Create an object using the mesh datablock o = bpy.data.objects.new("Floor", m) - - # Add your properties on mesh datablock d = m.archipack_floor.add() - - # Link object into scene + # make manipulators selectable + d.manipulable_selectable = True + angle_90 = pi / 2 + x, y, = 4, 4 + p = d.parts.add() + p.a0 = - angle_90 + p.length = y + p = d.parts.add() + p.a0 = angle_90 + p.length = x + p = d.parts.add() + p.a0 = angle_90 + p.length = y + p = d.parts.add() + p.a0 = angle_90 + p.length = x + d.n_parts = 4 context.scene.objects.link(o) - - # select and make active o.select = True context.scene.objects.active = o - - # Load preset into datablock self.load_preset(d) - - # add a material self.add_material(o) return o @@ -1125,11 +1811,174 @@ class ARCHIPACK_OT_floor(ArchipackCreateTool, Operator): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") o = self.create(context) - o.location = bpy.context.scene.cursor_location + o.location = context.scene.cursor_location + # activate manipulators at creation time o.select = True context.scene.objects.active = o + bpy.ops.archipack.floor_manipulate() + return {'FINISHED'} + else: + self.report({'WARNING'}, "Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_floor_from_curve(ArchipackCreateTool, Operator): + bl_idname = "archipack.floor_from_curve" + bl_label = "Floor curve" + bl_description = "Create a floor from a curve" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return context.active_object is not None and context.active_object.type == 'CURVE' + # ----------------------------------------------------- + # Draw (create UI interface) + # ----------------------------------------------------- + # noinspection PyUnusedLocal + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def create(self, context): + curve = context.active_object + bpy.ops.archipack.floor(auto_manipulate=self.auto_manipulate, filepath=self.filepath) + o = context.active_object + d = archipack_floor.datablock(o) + d.user_defined_path = curve.name + return o + + # ----------------------------------------------------- + # Execute + # ----------------------------------------------------- + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + self.create(context) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} - # Start manipulate mode + +class ARCHIPACK_OT_floor_from_wall(ArchipackCreateTool, Operator): + bl_idname = "archipack.floor_from_wall" + bl_label = "->Floor" + bl_description = "Create a floor from a wall" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + o = context.active_object + return o is not None and o.data is not None and 'archipack_wall2' in o.data + + def create(self, context): + wall = context.active_object + wd = wall.data.archipack_wall2[0] + bpy.ops.archipack.floor(auto_manipulate=False, filepath=self.filepath) + o = context.scene.objects.active + d = archipack_floor.datablock(o) + d.auto_update = False + d.closed = True + d.parts.clear() + d.n_parts = wd.n_parts + 1 + for part in wd.parts: + p = d.parts.add() + if "S_" in part.type: + p.type = "S_SEG" + else: + p.type = "C_SEG" + p.length = part.length + p.radius = part.radius + p.da = part.da + p.a0 = part.a0 + d.auto_update = True + # pretranslate + o.matrix_world = wall.matrix_world.copy() + return o + + # ----------------------------------------------------- + # Execute + # ----------------------------------------------------- + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.select = True + context.scene.objects.active = o + if self.auto_manipulate: + bpy.ops.archipack.floor_manipulate('INVOKE_DEFAULT') + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_floor_cutter(ArchipackCreateTool, Operator): + bl_idname = "archipack.floor_cutter" + bl_label = "Floor Cutter" + bl_description = "Floor Cutter" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + parent = StringProperty("") + + def create(self, context): + m = bpy.data.meshes.new("Floor Cutter") + o = bpy.data.objects.new("Floor Cutter", m) + d = m.archipack_floor_cutter.add() + parent = context.scene.objects.get(self.parent) + if parent is not None: + o.parent = parent + bbox = parent.bound_box + angle_90 = pi / 2 + x0, y0, z = bbox[0] + x1, y1, z = bbox[6] + x = 0.2 * (x1 - x0) + y = 0.2 * (y1 - y0) + o.matrix_world = parent.matrix_world * Matrix([ + [1, 0, 0, -3 * x], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]) + p = d.parts.add() + p.a0 = - angle_90 + p.length = y + p = d.parts.add() + p.a0 = angle_90 + p.length = x + p = d.parts.add() + p.a0 = angle_90 + p.length = y + d.n_parts = 3 + # d.close = True + pd = archipack_floor.datablock(parent) + pd.boundary = o.name + else: + o.location = context.scene.cursor_location + # make manipulators selectable + d.manipulable_selectable = True + context.scene.objects.link(o) + o.select = True + context.scene.objects.active = o + # self.add_material(o) + self.load_preset(d) + update_operation(d, context) + return o + + # ----------------------------------------------------- + # Execute + # ----------------------------------------------------- + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.select = True + context.scene.objects.active = o self.manipulate() return {'FINISHED'} else: @@ -1137,8 +1986,13 @@ class ARCHIPACK_OT_floor(ArchipackCreateTool, Operator): return {'CANCELLED'} +# ------------------------------------------------------------------ +# Define operator class to manipulate object +# ------------------------------------------------------------------ + + class ARCHIPACK_OT_floor_preset_menu(PresetMenuOperator, Operator): - bl_description = "Show Floor Presets" + bl_description = "Show Floor presets" bl_idname = "archipack.floor_preset_menu" bl_label = "Floor preset" preset_subdir = "archipack_floor" @@ -1152,7 +2006,7 @@ class ARCHIPACK_OT_floor_preset(ArchipackPreset, Operator): @property def blacklist(self): - return ['manipulators', 'over_length', 'over_width'] + return ['manipulators', 'parts', 'n_parts', 'user_defined_path', 'user_defined_resolution'] class ARCHIPACK_OT_floor_manipulate(Operator): @@ -1171,7 +2025,31 @@ class ARCHIPACK_OT_floor_manipulate(Operator): return {'FINISHED'} +class ARCHIPACK_OT_floor_cutter_manipulate(Operator): + bl_idname = "archipack.floor_cutter_manipulate" + bl_label = "Manipulate" + bl_description = "Manipulate" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_floor_cutter.filter(context.active_object) + + def invoke(self, context, event): + d = archipack_floor_cutter.datablock(context.active_object) + d.manipulable_invoke(context) + return {'FINISHED'} + + def register(): + bpy.utils.register_class(archipack_floor_cutter_segment) + bpy.utils.register_class(archipack_floor_cutter) + Mesh.archipack_floor_cutter = CollectionProperty(type=archipack_floor_cutter) + bpy.utils.register_class(ARCHIPACK_OT_floor_cutter) + bpy.utils.register_class(ARCHIPACK_PT_floor_cutter) + bpy.utils.register_class(ARCHIPACK_OT_floor_cutter_manipulate) + + bpy.utils.register_class(archipack_floor_part) bpy.utils.register_class(archipack_floor) Mesh.archipack_floor = CollectionProperty(type=archipack_floor) bpy.utils.register_class(ARCHIPACK_PT_floor) @@ -1179,9 +2057,19 @@ def register(): bpy.utils.register_class(ARCHIPACK_OT_floor_preset_menu) bpy.utils.register_class(ARCHIPACK_OT_floor_preset) bpy.utils.register_class(ARCHIPACK_OT_floor_manipulate) + bpy.utils.register_class(ARCHIPACK_OT_floor_from_curve) + bpy.utils.register_class(ARCHIPACK_OT_floor_from_wall) def unregister(): + bpy.utils.unregister_class(archipack_floor_cutter_segment) + bpy.utils.unregister_class(archipack_floor_cutter) + del Mesh.archipack_floor_cutter + bpy.utils.unregister_class(ARCHIPACK_OT_floor_cutter) + bpy.utils.unregister_class(ARCHIPACK_PT_floor_cutter) + bpy.utils.unregister_class(ARCHIPACK_OT_floor_cutter_manipulate) + + bpy.utils.unregister_class(archipack_floor_part) bpy.utils.unregister_class(archipack_floor) del Mesh.archipack_floor bpy.utils.unregister_class(ARCHIPACK_PT_floor) @@ -1189,3 +2077,5 @@ def unregister(): bpy.utils.unregister_class(ARCHIPACK_OT_floor_preset_menu) bpy.utils.unregister_class(ARCHIPACK_OT_floor_preset) bpy.utils.unregister_class(ARCHIPACK_OT_floor_manipulate) + bpy.utils.unregister_class(ARCHIPACK_OT_floor_from_curve) + bpy.utils.unregister_class(ARCHIPACK_OT_floor_from_wall) |