From d5b1f52c1c169da4bcc10d30ebc19fdf57a7fac9 Mon Sep 17 00:00:00 2001 From: s-leger Date: Tue, 13 Jun 2017 15:08:46 +0200 Subject: [PATCH 01/17] [FEATURE] draw tools handle keyboard and undo --- archipack_door.py | 26 +++++++++++++++++++++----- archipack_window.py | 26 +++++++++++++++++++++----- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/archipack_door.py b/archipack_door.py index f5437a5..ea92428 100644 --- a/archipack_door.py +++ b/archipack_door.py @@ -43,6 +43,7 @@ from .archipack_preset import ArchipackPreset, PresetMenuOperator from .archipack_object import ArchipackCreateTool, ArchipackObject from .archipack_gl import FeedbackPanel +from .archipack_keymaps import Keymaps from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d SPACING = 0.005 @@ -1679,9 +1680,10 @@ class ARCHIPACK_OT_door_draw(Operator): bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - feedback = None filepath = StringProperty(default="") - + feedback = None + stack = [] + def mouse_to_matrix(self, context, event): """ convert mouse pos to 3d point over plane defined by origin and normal @@ -1744,7 +1746,7 @@ def modal(self, context, event): w.matrix_world = tM if event.value == 'PRESS': - if event.type in {'LEFTMOUSE'}: + if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}: if wall is not None: context.scene.objects.active = wall wall.select = True @@ -1754,6 +1756,7 @@ def modal(self, context, event): # w must be a door here if archipack_door.filter(w): context.scene.objects.active = w + self.stack.append(w) self.add_object(context, event) context.active_object.matrix_world = tM return {'RUNNING_MODAL'} @@ -1761,9 +1764,20 @@ def modal(self, context, event): # prevent selection of other object if event.type in {'RIGHTMOUSE'}: return {'RUNNING_MODAL'} - + + if self.keymap.check(event, self.keymap.undo) or ( + event.type in {'BACK_SPACE'} and event.value == 'RELEASE' + ): + if len(self.stack) > 0: + o = context.active_object + last = self.stack.pop() + context.scene.objects.active = last + bpy.ops.archipack.door(mode="DELETE") + context.scene.objects.active = o + return {'RUNNING_MODAL'} + if event.value == 'RELEASE': - + if event.type in {'ESC', 'RIGHTMOUSE'}: bpy.ops.archipack.door(mode='DELETE') self.feedback.disable() @@ -1775,6 +1789,8 @@ def modal(self, context, event): def invoke(self, context, event): if context.mode == "OBJECT": + self.stack = [] + self.keymap = Keymaps(context) bpy.ops.archipack.disable_manipulate() bpy.ops.object.select_all(action="DESELECT") self.add_object(context, event) diff --git a/archipack_window.py b/archipack_window.py index 8b3aa23..87bda2f 100644 --- a/archipack_window.py +++ b/archipack_window.py @@ -43,6 +43,7 @@ from .archipack_preset import ArchipackPreset, PresetMenuOperator from .archipack_gl import FeedbackPanel from .archipack_object import ArchipackCreateTool, ArchipackObject +from .archipack_keymaps import Keymaps from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d @@ -1815,9 +1816,9 @@ class ARCHIPACK_OT_window_draw(Operator): bl_options = {'REGISTER', 'UNDO'} filepath = StringProperty(default="") - feedback = None - + stack = [] + def mouse_to_matrix(self, context, event): """ convert mouse pos to 3d point over plane defined by origin and normal @@ -1870,7 +1871,7 @@ def add_object(self, context, event): context.scene.objects.active = new_w else: bpy.ops.archipack.window(auto_manipulate=False, filepath=self.filepath) - + def modal(self, context, event): context.area.tag_redraw() @@ -1880,7 +1881,7 @@ def modal(self, context, event): w.matrix_world = tM if event.value == 'PRESS': - if event.type in {'LEFTMOUSE'}: + if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}: if wall is not None: context.scene.objects.active = wall wall.select = True @@ -1890,14 +1891,27 @@ def modal(self, context, event): # w must be a window here if archipack_window.filter(w): context.scene.objects.active = w + self.stack.append(w) self.add_object(context, event) context.active_object.matrix_world = tM return {'RUNNING_MODAL'} # prevent selection of other object if event.type in {'RIGHTMOUSE'}: return {'RUNNING_MODAL'} - + + if self.keymap.check(event, self.keymap.undo) or ( + event.type in {'BACK_SPACE'} and event.value == 'RELEASE' + ): + if len(self.stack) > 0: + o = context.active_object + last = self.stack.pop() + context.scene.objects.active = last + bpy.ops.archipack.window(mode="DELETE") + context.scene.objects.active = o + return {'RUNNING_MODAL'} + if event.value == 'RELEASE': + if event.type in {'ESC', 'RIGHTMOUSE'}: bpy.ops.archipack.window(mode='DELETE') self.feedback.disable() @@ -1909,6 +1923,8 @@ def modal(self, context, event): def invoke(self, context, event): if context.mode == "OBJECT": + self.stack = [] + self.keymap = Keymaps(context) # exit manipulate_mode if any bpy.ops.archipack.disable_manipulate() bpy.ops.object.select_all(action="DESELECT") From c3213e4d4a93c2e7e9bd326463b47e75894f883e Mon Sep 17 00:00:00 2001 From: s-leger Date: Tue, 13 Jun 2017 15:33:17 +0200 Subject: [PATCH 02/17] [FEATURE] Ensure wall ccw vertex order on draw --- archipack_wall2.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/archipack_wall2.py b/archipack_wall2.py index 775b704..27398ca 100644 --- a/archipack_wall2.py +++ b/archipack_wall2.py @@ -535,6 +535,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): default=1, update=update_manipulators ) step_angle = FloatProperty( + description="Curved parts segmentation", name="step angle", min=1 / 180 * pi, max=pi, @@ -1628,6 +1629,53 @@ def sp_init(self, context, event, state, sp): self.state = state return + def ensure_ccw(self): + """ + Wall to slab expect wall vertex order to be ccw + so reverse order here when needed + """ + d = archipack_wall2.datablock(self.o) + g = d.get_generator() + pts = [seg.p0.to_3d() for seg in g.segs] + + if d.closed: + pts.append(pts[0]) + + if d.is_cw(pts): + d.x_offset = 1 + pts = list(reversed(pts)) + self.o.location += pts[0] - pts[-1] + + d.auto_update = False + + d.n_parts = len(pts) - 1 + + if d.closed: + d.n_parts -= 1 + + d.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(d.parts): + print("Too many pts for parts") + break + p = d.parts[i] + p.length = dp.to_2d().length + p.dz = dp.z + p.a0 = da + a0 += da + p0 = p1 + + d.auto_update = True + def modal(self, context, event): context.area.tag_redraw() @@ -1706,6 +1754,9 @@ def modal(self, context, event): bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') if self.o is not None: + self.o.select = True + context.scene.objects.active = self.o + self.ensure_ccw() self.o.select = True context.scene.objects.active = self.o if bpy.ops.archipack.wall2_manipulate.poll(): From e0eec4af72d033649531352766ffd40667ef03cb Mon Sep 17 00:00:00 2001 From: s-leger Date: Wed, 14 Jun 2017 01:04:36 +0200 Subject: [PATCH 03/17] [FEATURE] invoke draw with shift make linked dups Select the window or door you want to dup as linked object. Press shift and invoke draw tool to clone selected as linked. --- archipack_door.py | 9 ++++- archipack_preset.py | 10 +++++ archipack_slab.py | 90 ++++++++++++++++++++++++++++----------------- archipack_window.py | 10 ++++- 4 files changed, 83 insertions(+), 36 deletions(-) diff --git a/archipack_door.py b/archipack_door.py index ea92428..28919c9 100644 --- a/archipack_door.py +++ b/archipack_door.py @@ -1789,14 +1789,21 @@ def modal(self, context, event): def invoke(self, context, event): if context.mode == "OBJECT": + o = None self.stack = [] self.keymap = Keymaps(context) bpy.ops.archipack.disable_manipulate() + if event.shift and archipack_door.filter(context.active_object): + o = context.active_object bpy.ops.object.select_all(action="DESELECT") + if o is not None: + o.select = True + context.scene.objects.active = o self.add_object(context, event) self.feedback = FeedbackPanel() self.feedback.instructions(context, "Draw a door", "Click & Drag over a wall", [ - ('LEFTCLICK', 'Create a door'), + ('LEFTCLICK, RET, SPACE, ENTER', 'Create a door'), + ('BACKSPACE, CTRL+Z', 'undo last'), ('SHIFT', 'Make linked door'), ('RIGHTCLICK or ESC', 'exit') ]) diff --git a/archipack_preset.py b/archipack_preset.py index e2bf4c1..a6c1738 100644 --- a/archipack_preset.py +++ b/archipack_preset.py @@ -432,6 +432,16 @@ def modal(self, context, event): def invoke(self, context, event): if context.area.type == 'VIEW_3D': + + # with shift pressed on invoke, will bypass menu operator and + # call preset_operator + if event.shift: + po = self.preset_operator.split(".") + op = getattr(getattr(bpy.ops, po[0]), po[1]) + if op.poll(): + op('INVOKE_DEFAULT') + return {'FINISHED'} + self.menu = PresetMenu(context, self.preset_subdir) # the arguments we pass the the callback diff --git a/archipack_slab.py b/archipack_slab.py index 0f6db00..a9a9b5f 100644 --- a/archipack_slab.py +++ b/archipack_slab.py @@ -47,6 +47,13 @@ class Slab(): def __init__(self): pass + def set_offset(self, offset, last=None): + """ + Offset line and compute intersection point + between segments + """ + self.line = self.make_offset(offset, last) + def straight_slab(self, a0, length): s = self.straight(length).rotate(a0) return StraightSlab(s.p, s.v) @@ -80,50 +87,44 @@ def __init__(self, parts): self.parts = parts self.segs = [] - def add_part(self, type, radius, a0, da, length, offset): + def add_part(self, part): if len(self.segs) < 1: s = None else: s = self.segs[-1] - # start a new slab if s is None: - if type == 'S_SEG': + if part.type == 'S_SEG': p = Vector((0, 0)) - v = length * Vector((cos(a0), sin(a0))) + v = part.length * Vector((cos(part.a0), sin(part.a0))) s = StraightSlab(p, v) - elif type == 'C_SEG': - c = -radius * Vector((cos(a0), sin(a0))) - s = CurvedSlab(c, radius, a0, da) + elif part.type == 'C_SEG': + c = -part.radius * Vector((cos(part.a0), sin(part.a0))) + s = CurvedSlab(c, part.radius, part.a0, part.da) else: - if type == 'S_SEG': - s = s.straight_slab(a0, length) - elif type == 'C_SEG': - s = s.curved_slab(a0, da, radius) + if part.type == 'S_SEG': + s = s.straight_slab(part.a0, part.length) + elif part.type == 'C_SEG': + s = s.curved_slab(part.a0, part.da, part.radius) self.segs.append(s) - self.last_type = type + 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 + 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 + return def locate_manipulators(self): """ @@ -163,10 +164,10 @@ def get_verts(self, verts): for slab in self.segs: if "Curved" in type(slab).__name__: for i in range(16): - x, y = slab.lerp(i / 16) + x, y = slab.line.lerp(i / 16) verts.append((x, y, 0)) else: - x, y = slab.p0 + x, y = slab.line.p0 verts.append((x, y, 0)) """ for i in range(33): @@ -265,10 +266,7 @@ def update_type(self, context): w = w0.curved_slab(part.a0, part.da, part.radius) else: g = SlabGenerator(None) - if "C_" in self.type: - g.add_part("S_SEG", self.radius, self.a0, self.da, self.length, 0) - else: - g.add_part("C_SEG", self.radius, self.a0, self.da, self.length, 0) + g.add_part(self) w = g.segs[0] # w0 - w - w1 @@ -347,6 +345,19 @@ class ArchipackSegment(): subtype='ANGLE', unit='ROTATION', update=update ) + offset = FloatProperty( + name="Offset", + description="Add to current segment offset", + default=0, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + + # @TODO: + # flag to handle wall's x_offset + # when set add wall offset value to segment offset + # pay attention at allowing per wall segment offset + manipulators = CollectionProperty(type=archipack_manipulator) def find_in_selection(self, context): @@ -378,6 +389,8 @@ def draw(self, context, layout, index): row.prop(self, "length") row = box.row() row.prop(self, "a0") + row = box.row() + row.prop(self, "offset") class archipack_slab_part(ArchipackSegment, PropertyGroup): @@ -435,6 +448,11 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup): unit='LENGTH', subtype='DISTANCE', update=update ) + + # @TODO: + # Global slab offset + # will only affect slab parts sharing a wall + childs = CollectionProperty(type=archipack_slab_child) # Flag to prevent mesh update while making bulk changes over variables # use : @@ -451,7 +469,9 @@ def get_generator(self): g = SlabGenerator(self.parts) for part in self.parts: # type, radius, da, length - g.add_part(part.type, part.radius, part.a0, part.da, part.length, 0) + g.add_part(part) + + g.set_offset() g.close(self.closed) g.locate_manipulators() @@ -468,6 +488,7 @@ def insert_part(self, context, where): part_1 = self.parts[len(self.parts) - 1] part_1.type = part_0.type part_1.length = part_0.length + part_1.offset = part_0.offset part_1.da = part_0.da part_1.a0 = 0 # move after current one @@ -525,6 +546,7 @@ def insert_balcony(self, context, where): part_1.type = part_0.type part_1.length = part_0.length part_1.radius = part_0.radius + part_1.offset = part_0.offset part_1.da = part_0.da part_1.a0 = -pi / 2 # move after current one diff --git a/archipack_window.py b/archipack_window.py index 87bda2f..8c64480 100644 --- a/archipack_window.py +++ b/archipack_window.py @@ -1923,15 +1923,23 @@ def modal(self, context, event): def invoke(self, context, event): if context.mode == "OBJECT": + o = None self.stack = [] self.keymap = Keymaps(context) # exit manipulate_mode if any bpy.ops.archipack.disable_manipulate() + # invoke with shift pressed will use current object as basis for linked copy + if event.shift and archipack_window.filter(context.active_object): + o = context.active_object bpy.ops.object.select_all(action="DESELECT") + if o is not None: + o.select = True + context.scene.objects.active = o self.add_object(context, event) self.feedback = FeedbackPanel() self.feedback.instructions(context, "Draw a window", "Click & Drag over a wall", [ - ('LEFTCLICK', 'Create a window'), + ('LEFTCLICK, RET, SPACE, ENTER', 'Create a window'), + ('BACKSPACE, CTRL+Z', 'undo last'), ('SHIFT', 'Make linked window'), ('RIGHTCLICK or ESC', 'exit') ]) From 3ed5c5ddcb768199fa95a33b6a445f0dd5351225 Mon Sep 17 00:00:00 2001 From: s-leger Date: Sat, 17 Jun 2017 19:22:35 +0200 Subject: [PATCH 04/17] [FEATURE] toolkit and slab/wall synchro --- archipack_2d.py | 138 ++++++++++----- archipack_autoboolean.py | 58 +++++-- archipack_door.py | 134 ++++----------- archipack_fence.py | 76 +++----- archipack_manipulator.py | 136 +++++---------- archipack_object.py | 35 +++- archipack_reference_point.py | 5 +- archipack_slab.py | 301 ++++++++++++++++++++++++++------ archipack_stair.py | 37 ++-- archipack_toolkit.py | 325 +++++++++++++++++++++++++++++++++++ archipack_truss.py | 25 +-- archipack_wall.py | 43 ++--- archipack_wall2.py | 227 +++++++++++++----------- archipack_window.py | 150 ++++------------ bmesh_utils.py | 10 +- 15 files changed, 1031 insertions(+), 669 deletions(-) create mode 100644 archipack_toolkit.py diff --git a/archipack_2d.py b/archipack_2d.py index e9b2954..6862da9 100644 --- a/archipack_2d.py +++ b/archipack_2d.py @@ -26,9 +26,16 @@ # ---------------------------------------------------------- from mathutils import Vector, Matrix from math import sin, cos, pi, atan2, sqrt, acos +import bpy +# allow to draw parts with gl for debug puropses +from .archipack_gl import GlBaseLine -class Projection(): +class Projection(GlBaseLine): + + def __init__(self): + GlBaseLine.__init__(self) + def proj_xy(self, t, next=None): """ length of projection of sections at crossing line / circle intersections @@ -99,6 +106,7 @@ def __init__(self, p=None, v=None, p0=None, p1=None): Will convert any into Vector 2d both optionnals """ + Projection.__init__(self) if p is not None and v is not None: self.p = Vector(p).to_2d() self.v = Vector(v).to_2d() @@ -183,13 +191,9 @@ def cross_z(self): def cross(self): return Vector((self.v.y, -self.v.x)) - @property - def pts(self): - return [self.p0, self.p1] - def signed_angle(self, u, v): """ - signed angle between two vectors + signed angle between two vectors range [-pi, pi] """ return atan2(u.x * v.y - u.y * v.x, u.x * v.x + u.y * v.y) @@ -200,7 +204,7 @@ def delta_angle(self, last): """ if last is None: return self.angle - return self.signed_angle(last.straight(1).v, self.straight(0).v) + return self.signed_angle(last.straight(1, 1).v, self.straight(1, 0).v) def normal(self, t=0): """ @@ -287,12 +291,19 @@ def tangeant(self, t, da, radius): def straight(self, length, t=1): return Line(self.lerp(t), self.v.normalized() * length) - def rotate(self, da): - cs = cos(da) - sn = sin(da) - x, y = self.v - self.v.x = x * cs - y * sn - self.v.y = x * sn + y * cs + def translate(self, dp): + self.p += dp + + def rotate(self, a): + """ + Rotate segment ccw arroud p0 + """ + ca = cos(a) + sa = sin(a) + self.v = Matrix([ + [ca, -sa], + [sa, ca] + ]) * self.v return self def scale(self, length): @@ -302,12 +313,12 @@ def scale(self, length): def tangeant_unit_vector(self, t): return self.v.normalized() - def draw(self, context): + def as_curve(self, context): """ Draw Line with open gl in screen space aka: coords are in pixels """ - return NotImplementedError + raise NotImplementedError def make_offset(self, offset, last=None): """ @@ -319,7 +330,7 @@ def make_offset(self, offset, last=None): if last is None: return line - if type(last).__name__ == 'Arc': + if hasattr(last, "r"): res, d, t = line.point_sur_segment(last.c) c = (last.r * last.r) - (d * d) print("t:%s" % t) @@ -369,9 +380,14 @@ def make_offset(self, offset, last=None): return line + @property + def pts(self): + return [self.p0.to_3d(), self.p1.to_3d()] + class Circle(Projection): def __init__(self, c, radius): + Projection.__init__(self) self.r = radius self.r2 = radius * radius self.c = c @@ -399,6 +415,9 @@ def intersect(self, line): else: return True, line.lerp(t1), t1 + def translate(self, dp): + self.c += dp + class Arc(Circle): """ @@ -450,9 +469,9 @@ def delta_angle(self, last): """ if last is None: return self.a0 - return self.signed_angle(last.straight(1).v, self.straight(0).v) + return self.signed_angle(last.straight(1, 1).v, self.straight(1, 0).v) - def rot_scale_matrix(self, u, v): + def scale_rot_matrix(self, u, v): """ given vector u and v (from and to p0 p1) apply scale factor to radius and @@ -467,8 +486,8 @@ def rot_scale_matrix(self, u, v): ca = scale * cos(a) sa = scale * sin(a) return scale, Matrix([ - [ca, sa], - [-sa, ca] + [ca, -sa], + [sa, ca] ]) @property @@ -493,11 +512,11 @@ def p0(self, p0): """ u = self.p0 - self.p1 v = p0 - self.p1 - scale, tM = self.rot_scale_matrix(u, v) - self.c = self.p1 + tM * (self.c - self.p1) + scale, rM = self.scale_rot_matrix(u, v) + self.c = self.p1 + rM * (self.c - self.p1) self.r *= scale self.r2 = self.r * self.r - dp = self.p0 - self.c + dp = p0 - self.c self.a0 = atan2(dp.y, dp.x) @p1.setter @@ -506,13 +525,15 @@ def p1(self, p1): rotate and scale arc so it intersect p0 p1 da is not affected """ - u = self.p1 - self.p0 - v = p1 - self.p0 - scale, tM = self.rot_scale_matrix(u, v) - self.c = self.p0 + tM * (self.c - self.p0) + p0 = self.p0 + u = self.p1 - p0 + v = p1 - p0 + + scale, rM = self.scale_rot_matrix(u, v) + self.c = p0 + rM * (self.c - p0) self.r *= scale self.r2 = self.r * self.r - dp = self.p0 - self.c + dp = p0 - self.c self.a0 = atan2(dp.y, dp.x) @property @@ -607,8 +628,7 @@ def straight(self, length, t=1): Return a tangeant Line Counterpart on Line also return a Line """ - s = self.tangeant(t, length) - return s + return self.tangeant(t, length) def point_sur_segment(self, pt): """ @@ -624,26 +644,22 @@ def point_sur_segment(self, pt): t = (a - self.a0) / self.da return t > 0 and t < 1, d, t - def rotate(self, da): + def rotate(self, a): """ - Rotate center so we rotate arround start + Rotate center so we rotate ccw arround p0 """ - cs = cos(da) - sn = sin(da) + ca = cos(a) + sa = sin(a) rM = Matrix([ - [cs, sn], - [-sn, cs] + [ca, -sa], + [sa, ca] ]) - self.c = rM * (self.p0 - self.c) + p0 = self.p0 + self.c = p0 + rM * (self.c - p0) + dp = p0 - self.c + self.a0 = atan2(dp.y, dp.x) return self - def draw(self, context): - """ - Draw 2d arc with open gl in screen space - aka: coords are in pixels - """ - raise NotImplementedError - # make offset for line / arc, arc / arc def make_offset(self, offset, last=None): @@ -652,7 +668,7 @@ def make_offset(self, offset, last=None): if last is None: return line - if type(last).__name__ == 'Line': + if hasattr(last, "v"): # intersect line / arc # 1 line -> 2 arc res, d, t = last.point_sur_segment(line.c) @@ -728,6 +744,38 @@ def make_offset(self, offset, last=None): line.da = self.signed_angle(u, v) return line + # DEBUG + @property + def pts(self): + n_pts = max(1, int(round(abs(self.da) / pi * 30, 0))) + t_step = 1 / n_pts + return [self.lerp(i * t_step).to_3d() for i in range(n_pts + 1)] + + def as_curve(self, context): + """ + Draw 2d arc with open gl in screen space + aka: coords are in pixels + """ + curve = bpy.data.curves.new('ARC', type='CURVE') + curve.dimensions = '2D' + spline = curve.splines.new('POLY') + spline.use_endpoint_u = False + spline.use_cyclic_u = False + pts = self.pts + spline.points.add(len(pts) - 1) + for i, p in enumerate(pts): + x, y = p + spline.points[i].co = (x, y, 0, 1) + curve_obj = bpy.data.objects.new('ARC', curve) + context.scene.objects.link(curve_obj) + curve_obj.select = True + +""" +from archipack.archipack_2d import Arc +a = Arc(Vector((0, 0)), 5, 0, 1.5) +a.draw(C) +a.rotate(0.5).draw(C) +""" class Line3d(Line): """ diff --git a/archipack_autoboolean.py b/archipack_autoboolean.py index 7b9af5f..b1f5d5c 100644 --- a/archipack_autoboolean.py +++ b/archipack_autoboolean.py @@ -30,6 +30,15 @@ from mathutils import Vector from .materialutils import MaterialUtils +from os import path + + +def debug_using_gl(context, filename): + context.scene.update() + temp_path = "C:\\tmp\\" + context.scene.render.filepath = path.join(temp_path, filename + ".png") + bpy.ops.render.opengl(write_still=True) + class ArchipackBoolManager(): """ @@ -127,6 +136,7 @@ def _generate_hole(self, context, o): if self.mode != 'ROBUST': hole = self.get_child_hole(o) if hole is not None: + # print("_generate_hole Use existing hole %s" % (hole.name)) return hole # generate single hole from archipack primitives d = self.datablock(o) @@ -139,6 +149,7 @@ def _generate_hole(self, context, o): hole = d.interactive_hole(context, o) else: hole = d.robust_hole(context, o.matrix_world) + # print("_generate_hole Generate hole %s" % (hole.name)) return hole def partition(self, array, begin, end): @@ -173,6 +184,7 @@ def sort_holes(self, wall, holes): return [o[0] for o in holes] def difference(self, basis, hole, solver=None): + # print("difference %s" % (hole.name)) m = basis.modifiers.new('AutoBoolean', 'BOOLEAN') m.operation = 'DIFFERENCE' if solver is None: @@ -182,14 +194,18 @@ def difference(self, basis, hole, solver=None): m.object = hole def union(self, basis, hole): + # print("union %s" % (hole.name)) m = basis.modifiers.new('AutoMerge', 'BOOLEAN') m.operation = 'UNION' m.solver = self.solver_mode m.object = hole def remove_modif_and_object(self, context, o, to_delete): + # print("remove_modif_and_object removed:%s" % (len(to_delete))) for m, h in to_delete: if m is not None: + if m.object is not None: + m.object = None o.modifiers.remove(m) if h is not None: context.scene.objects.unlink(h) @@ -197,17 +213,18 @@ def remove_modif_and_object(self, context, o, to_delete): # Mixed def create_merge_basis(self, context, wall): + # print("create_merge_basis") h = bpy.data.meshes.new("AutoBoolean") hole_obj = bpy.data.objects.new("AutoBoolean", h) context.scene.objects.link(hole_obj) - hole_obj['archipack_hybriddhole'] = True + hole_obj['archipack_hybridhole'] = True if wall.parent is not None: hole_obj.parent = wall.parent hole_obj.matrix_world = wall.matrix_world.copy() MaterialUtils.add_wall2_materials(hole_obj) return hole_obj - def update_mixed(self, context, wall, childs, holes): + def update_hybrid(self, context, wall, childs, holes): """ Update all holes modifiers remove holes not found in childs @@ -248,10 +265,10 @@ def update_mixed(self, context, wall, childs, holes): hole_obj = self.create_merge_basis(context, wall) else: hole_obj = m.object - + # debug_using_gl(context, "260") m.object = hole_obj self.prepare_hole(hole_obj) - + # debug_using_gl(context, "263") to_delete = [] # mixed-> mixed @@ -261,15 +278,16 @@ def update_mixed(self, context, wall, childs, holes): existing.append(h) else: to_delete.append([m, h]) - + # remove modifier and holes not found in new list self.remove_modif_and_object(context, hole_obj, to_delete) - + # debug_using_gl(context, "276") # add modifier and holes not found in existing for h in holes: if h not in existing: self.union(hole_obj, h) - + # debug_using_gl(context, "281") + # Interactive def update_interactive(self, context, wall, childs, holes): @@ -282,7 +300,7 @@ def update_interactive(self, context, wall, childs, holes): # mixed-> interactive for m in wall.modifiers: if m.type == 'BOOLEAN': - if m.object is not None and 'archipack_hybriddhole' in m.object: + if m.object is not None and 'archipack_hybridhole' in m.object: hole_obj = m.object break @@ -337,7 +355,7 @@ def update_robust(self, context, wall, childs): to_delete.append([None, m.object]) elif 'archipack_hole' in m.object: to_delete.append([m, m.object]) - elif 'archipack_hybriddhole' in m.object: + elif 'archipack_hybridhole' in m.object: to_delete.append([m, m.object]) o = m.object for m in o.modifiers: @@ -382,21 +400,22 @@ def autoboolean(self, context, wall): if h is not None: holes.append(h) childs.append(o) - + # debug_using_gl(context, "395") self.sort_holes(wall, holes) # hole(s) are selected and active after this one for hole in holes: self.prepare_hole(hole) - + # debug_using_gl(context, "401") + # update / remove / add boolean modifier if self.mode == 'INTERACTIVE': self.update_interactive(context, wall, childs, holes) elif self.mode == 'ROBUST': self.update_robust(context, wall, childs) else: - self.update_mixed(context, wall, childs, holes) - + self.update_hybrid(context, wall, childs, holes) + bpy.ops.object.select_all(action='DESELECT') # parenting childs to wall reference point if wall.parent is None: @@ -408,25 +427,28 @@ def autoboolean(self, context, wall): else: wall.parent.select = True context.scene.objects.active = wall.parent - + # debug_using_gl(context, "422") wall.select = True for o in childs: if 'archipack_robusthole' in o: o.hide_select = False o.select = True - + # debug_using_gl(context, "428") + bpy.ops.archipack.parent_to_reference() for o in childs: if 'archipack_robusthole' in o: o.hide_select = True - + # debug_using_gl(context, "435") + + def detect_mode(self, context, wall): for m in wall.modifiers: if m.type == 'BOOLEAN' and m.object is not None: if 'archipack_hole' in m.object: self.mode = 'INTERACTIVE' - if 'archipack_hybriddhole' in m.object: + if 'archipack_hybridhole' in m.object: self.mode = 'HYBRID' if 'archipack_robusthole' in m.object: self.mode = 'ROBUST' @@ -435,6 +457,7 @@ def singleboolean(self, context, wall, o): """ Entry point for single boolean operations in use in draw door and windows over wall + o is either a window or a door """ # generate holes for crossing window and doors self.itM = wall.matrix_world.inverted() @@ -596,6 +619,7 @@ def execute(self, context): bpy.ops.object.select_all(action='DESELECT') for wall in walls: manager.autoboolean(context, wall) + bpy.ops.object.select_all(action='DESELECT') wall.select = True context.scene.objects.active = wall if wall.data is not None and 'archipack_wall2' in wall.data: diff --git a/archipack_door.py b/archipack_door.py index 28919c9..843ab8d 100644 --- a/archipack_door.py +++ b/archipack_door.py @@ -60,7 +60,7 @@ def update_childs(self, context): self.update(context, childs_only=True) -class archipack_door_panel(PropertyGroup): +class archipack_door_panel(ArchipackObject, PropertyGroup): x = FloatProperty( name='width', min=0.25, @@ -538,19 +538,6 @@ def matids(self): mat += back.mat(curve_steps, 0, 0, path_type=shape) return mat - def find_in_selection(self, context): - """ - find witch selected object this instance belongs to - provide support for "copy to selected" - """ - active = context.active_object - selected = [o for o in context.selected_objects] - for o in selected: - c, props = ARCHIPACK_PT_door_panel.params(o) - if props == self: - return active, selected, o - return active, selected, None - def find_handle(self, o): for child in o.children: if 'archipack_handle' in child: @@ -577,7 +564,8 @@ def remove_handle(self, context, o): bpy.data.objects.remove(handle, do_unlink=True) def update(self, context): - active, selected, o = self.find_in_selection(context) + o = self.find_in_selection(context) + if o is None: return @@ -588,9 +576,8 @@ def update(self, context): else: self.update_handle(context, o) - active.select = True - context.scene.objects.active = active - + self.restore_context(context) + class ARCHIPACK_PT_door_panel(Panel): bl_idname = "ARCHIPACK_PT_door_panel" @@ -600,55 +587,15 @@ class ARCHIPACK_PT_door_panel(Panel): # bl_context = 'object' bl_category = 'ArchiPack' + @classmethod + def poll(cls, context): + return archipack_door_panel.filter(context.active_object) + def draw(self, context): - """ - o = context.object - o, prop = ARCHIPACK_PT_door_panel.params(o) - if prop is None: - return - """ layout = self.layout - """ - layout.prop(prop, 'direction') - layout.prop(prop, 'model') - layout.prop(prop, 'chanfer') - layout.prop(prop, 'panel_bottom') - layout.prop(prop, 'panel_spacing') - layout.prop(prop, 'panel_border') - layout.prop(prop, 'panels_x') - layout.prop(prop, 'panels_y') - layout.prop(prop, 'panels_distrib') - layout.label(text="Show properties") - """ layout.operator("archipack.select_parent") - return - - @classmethod - def params(cls, o): - if cls.filter(o): - if 'archipack_doorpanel' in o.data: - return o, o.data.archipack_doorpanel[0] - else: - for child in o.children: - o, props = cls.params(child) - if props is not None: - return o, props - return o, None - - @classmethod - def filter(cls, o): - try: - return bool('archipack_doorpanel' in o.data) - except: - return False - - @classmethod - def poll(cls, context): - o = context.active_object - if o is None: - return False - return cls.filter(o) - + + # ------------------------------------------------------------------ # Define operator class to create object # ------------------------------------------------------------------ @@ -764,7 +711,7 @@ def create(self, context): """ m = bpy.data.meshes.new("Panel") o = bpy.data.objects.new("Panel", m) - d = m.archipack_doorpanel.add() + d = m.archipack_door_panel.add() d.x = self.x d.y = self.y d.z = self.z @@ -791,8 +738,6 @@ def create(self, context): context.scene.objects.active = o d.update(context) MaterialUtils.add_door_materials(o) - o.select = True - context.scene.objects.active = o o.lock_rotation[0] = True o.lock_rotation[1] = True return o @@ -800,7 +745,9 @@ def create(self, context): def execute(self, context): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") - self.create(context) + o = self.create(context) + o.select = True + context.scene.objects.active = o return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") @@ -1077,7 +1024,7 @@ def remove_childs(self, context, o, to_remove): for child in o.children: if to_remove < 1: return - if ARCHIPACK_PT_door_panel.filter(child): + if archipack_door_panel.filter(child): self.remove_handle(context, child) to_remove -= 1 context.scene.objects.unlink(child) @@ -1093,7 +1040,7 @@ def create_childs(self, context, o): n_childs = 0 for child in o.children: - if ARCHIPACK_PT_door_panel.filter(child): + if archipack_door_panel.filter(child): n_childs += 1 # remove child @@ -1133,13 +1080,8 @@ def find_handle(self, o): return None def get_childs_panels(self, context, o): - childs = [] - for child in o.children: - c, props = ARCHIPACK_PT_door_panel.params(child) - if props is not None: - childs.append(c) - return childs - + return [child for child in o.children if archipack_door_panel.filter(child)] + def _synch_childs(self, context, o, linked, childs): """ sub synch childs nodes of linked object @@ -1292,7 +1234,7 @@ def update_childs(self, context, o): child = childs[child_n - 1] child.select = True context.scene.objects.active = child - c, props = ARCHIPACK_PT_door_panel.params(child) + props = archipack_door_panel.datablock(child) if props is not None: props.x = x props.y = y @@ -1313,9 +1255,9 @@ def update_childs(self, context, o): def update(self, context, childs_only=False): # support for "copy to selected" - active, selected, o = self.find_in_selection(context) + o = self.find_in_selection(context, self.auto_update) - if o is None or not self.auto_update: + if o is None: return self.setup_manipulators() @@ -1338,16 +1280,7 @@ def update(self, context, childs_only=False): self.manipulators[2].set_pts([(x, -y, 0), (x, -y, self.z), (-1, 0, 0)]) # restore context - bpy.ops.object.select_all(action="DESELECT") - - try: - for o in selected: - o.select = True - except: - pass - - active.select = True - context.scene.objects.active = active + self.restore_context(context) def find_hole(self, o): for child in o.children: @@ -1403,6 +1336,10 @@ class ARCHIPACK_PT_door(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'ArchiPack' + + @classmethod + def poll(cls, context): + return archipack_door.filter(context.active_object) def draw(self, context): o = context.active_object @@ -1460,10 +1397,7 @@ def draw(self, context): box.prop(props, 'panel_border') box.prop(props, 'chanfer') - @classmethod - def poll(cls, context): - return archipack_door.filter(context.active_object) - + # ------------------------------------------------------------------ # Define operator class to create object # ------------------------------------------------------------------ @@ -1614,7 +1548,7 @@ def delete(self, context): if 'archipack_hole' in child: context.scene.objects.unlink(child) bpy.data.objects.remove(child, do_unlink=True) - elif child.data is not None and 'archipack_doorpanel' in child.data: + elif child.data is not None and 'archipack_door_panel' in child.data: for handle in child.children: if 'archipack_handle' in handle: context.scene.objects.unlink(handle) @@ -1642,7 +1576,7 @@ def unique(self, context): o.select = True for child in o.children: if 'archipack_hole' in child or (child.data is not None and - 'archipack_doorpanel' in child.data): + 'archipack_door_panel' in child.data): child.hide_select = False child.select = True if len(context.selected_objects) > 0: @@ -1846,14 +1780,14 @@ def invoke(self, context, event): class ARCHIPACK_OT_door_preset_menu(PresetMenuOperator, Operator): bl_idname = "archipack.door_preset_menu" - bl_label = "Door Styles" + bl_label = "Door Presets" preset_subdir = "archipack_door" class ARCHIPACK_OT_door_preset(ArchipackPreset, Operator): """Add a Door Preset""" bl_idname = "archipack.door_preset" - bl_label = "Add Door Style" + bl_label = "Add Door Preset" preset_menu = "ARCHIPACK_OT_door_preset_menu" @property @@ -1864,7 +1798,7 @@ def blacklist(self): def register(): bpy.utils.register_class(archipack_door_panel) - Mesh.archipack_doorpanel = CollectionProperty(type=archipack_door_panel) + Mesh.archipack_door_panel = CollectionProperty(type=archipack_door_panel) bpy.utils.register_class(ARCHIPACK_PT_door_panel) bpy.utils.register_class(ARCHIPACK_OT_door_panel) bpy.utils.register_class(ARCHIPACK_OT_select_parent) @@ -1880,7 +1814,7 @@ def register(): def unregister(): bpy.utils.unregister_class(archipack_door_panel) - del Mesh.archipack_doorpanel + del Mesh.archipack_door_panel bpy.utils.unregister_class(ARCHIPACK_PT_door_panel) bpy.utils.unregister_class(ARCHIPACK_OT_door_panel) bpy.utils.unregister_class(ARCHIPACK_OT_select_parent) diff --git a/archipack_fence.py b/archipack_fence.py index 2d0f98f..b739104 100644 --- a/archipack_fence.py +++ b/archipack_fence.py @@ -33,7 +33,6 @@ StringProperty, EnumProperty, FloatVectorProperty ) from .bmesh_utils import BmeshEdit as bmed -from .materialutils import MaterialUtils from .panel import Panel as Lofter from mathutils import Vector, Matrix from mathutils.geometry import interpolate_bezier @@ -590,7 +589,7 @@ def update_path(self, context): def update_type(self, context): - d = self.find_in_selection(context) + d = self.find_datablock_in_selection(context) if d is not None and d.auto_update: @@ -663,7 +662,7 @@ class archipack_fence_material(PropertyGroup): update=update ) - def find_in_selection(self, context): + def find_datablock_in_selection(self, context): """ find witch selected object this instance belongs to provide support for "copy to selected" @@ -678,7 +677,7 @@ def find_in_selection(self, context): return None def update(self, context): - props = self.find_in_selection(context) + props = self.find_datablock_in_selection(context) if props is not None: props.update(context) @@ -730,7 +729,7 @@ class archipack_fence_part(PropertyGroup): manipulators = CollectionProperty(type=archipack_manipulator) - def find_in_selection(self, context): + def find_datablock_in_selection(self, context): """ find witch selected object this instance belongs to provide support for "copy to selected" @@ -745,7 +744,7 @@ def find_in_selection(self, context): return None def update(self, context, manipulable_refresh=False): - props = self.find_in_selection(context) + props = self.find_datablock_in_selection(context) if props is not None: props.update(context, manipulable_refresh) @@ -1290,9 +1289,9 @@ def get_generator(self): def update(self, context, manipulable_refresh=False): - active, selected, o = self.find_in_selection(context) + o = self.find_in_selection(context, self.auto_update) - if o is None or not self.auto_update: + if o is None: return # clean up manipulators before any data model change @@ -1375,20 +1374,13 @@ def update(self, context, manipulable_refresh=False): self.handrail_alt, self.handrail_extend, verts, faces, matids, uvs) bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs, weld=True, clean=True) - bpy.ops.object.shade_smooth() + # enable manipulators rebuild if manipulable_refresh: self.manipulable_refresh = True # restore context - try: - for o in selected: - o.select = True - except: - pass - - active.select = True - context.scene.objects.active = active + self.restore_context(context) def manipulable_setup(self, context): """ @@ -1431,6 +1423,10 @@ class ARCHIPACK_PT_fence(Panel): 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: @@ -1565,10 +1561,6 @@ def draw(self, context): else: row.prop(prop, 'idmats_expand', icon="TRIA_RIGHT", icon_only=True, text="Materials", emboss=False) - @classmethod - def poll(cls, context): - return archipack_fence.filter(context.active_object) - # ------------------------------------------------------------------ # Define operator class to create object # ------------------------------------------------------------------ @@ -1594,9 +1586,6 @@ def create(self, context): self.add_material(o) return o - # ----------------------------------------------------- - # Execute - # ----------------------------------------------------- def execute(self, context): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") @@ -1625,19 +1614,12 @@ class ARCHIPACK_OT_fence_curve_update(Operator): @classmethod def poll(self, context): return archipack_fence.filter(context.active_object) - # ----------------------------------------------------- - # 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') - # ----------------------------------------------------- - # Execute - # ----------------------------------------------------- def execute(self, context): if context.mode == "OBJECT": d = archipack_fence.datablock(context.active_object) @@ -1648,22 +1630,16 @@ def execute(self, context): return {'CANCELLED'} -class ARCHIPACK_OT_fence_from_curve(Operator): +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'} - auto_manipulate = BoolProperty(default=True) - @classmethod def poll(self, context): return context.active_object is not None and context.active_object.type == 'CURVE' - # ----------------------------------------------------- - # Draw (create UI interface) - # ----------------------------------------------------- - # noinspection PyUnusedLocal def draw(self, context): layout = self.layout @@ -1672,17 +1648,11 @@ def draw(self, context): def create(self, context): curve = context.active_object - 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 + bpy.ops.archipack.fence('INVOKE_DEFAULT', auto_manipulate=False) + o = context.active_object + d = archipack_fence.datablock(o) d.user_defined_path = curve.name - context.scene.objects.link(o) - o.select = True - context.scene.objects.active = o d.update_path(context) - MaterialUtils.add_stair_materials(o) spline = curve.data.splines[0] if spline.type == 'POLY': pt = spline.points[0].co @@ -1697,19 +1667,15 @@ def create(self, context): [0, 0, 1, pt.z], [0, 0, 0, 1] ]) - o.select = True - context.scene.objects.active = o return o - # ----------------------------------------------------- - # Execute - # ----------------------------------------------------- def execute(self, context): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") - self.create(context) - if self.auto_manipulate: - bpy.ops.archipack.fence_manipulate('INVOKE_DEFAULT') + o = self.create(context) + o.select = True + context.scene.objects.active = o + self.manipulate() return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") diff --git a/archipack_manipulator.py b/archipack_manipulator.py index 6a9a8a8..6ba7541 100644 --- a/archipack_manipulator.py +++ b/archipack_manipulator.py @@ -619,118 +619,68 @@ def sp_callback(self, context, event, state, sp): # rotation relative to object rM = self.o.matrix_world.inverted().to_3x3() + delta = (rM * sp.delta).to_2d() + x_axis = (rM * Vector((1, 0, 0))).to_2d() + + # update generator idx = 0 for p0, p1, selected in gl_pts3d: if selected: - # new point in object space - pt = g.segs[idx].lerp(0) + (rM * sp.delta).to_2d() - da = 0 + # new location in object space + pt = g.segs[idx].lerp(0) + delta + + # move last point of segment before current + if idx > 0: + g.segs[idx - 1].p1 = pt + + # move first point of current segment + g.segs[idx].p0 = pt + + idx += 1 + + # update properties from generator + idx = 0 + for p0, p1, selected in gl_pts3d: + + if selected: - # adjust size and rotation of segment before current + # adjust segment before current if idx > 0: w = g.segs[idx - 1] part = d.parts[idx - 1] - """ - w.p1 = pt - dp = pt - w.p0 if idx > 1: - part.a0 = g.segs[idx - 2].delta_angle(w) + part.a0 = w.delta_angle(g.segs[idx - 2]) else: - a0 = part.a0 + w.signed_angle(w.straight(0).v, dp) - if a0 > pi: - a0 -= 2 * pi - if a0 < -pi: - a0 += 2 * pi - # print("a0:%.4f part.a0:%.4f da:%.4f" % (a0, part.a0, da)) - part.a0 = a0 - + part.a0 = w.straight(1, 0).angle + if "C_" in part.type: part.radius = w.r else: part.length = w.length - """ - - dp = pt - w.p0 - - # adjust radius from distance between points.. - # use p0-p1 distance as reference - if "C_" in part.type: - dw = (w.p1 - w.p0) - part.radius = part.radius / dw.length * dp.length - # angle pt - p0 - angle p0 p1 - da = atan2(dp.y, dp.x) - atan2(dw.y, dw.x) - else: - part.length = dp.length - da = atan2(dp.y, dp.x) - w.straight(1).angle - a0 = part.a0 + da - if a0 > pi: - a0 -= 2 * pi - if a0 < -pi: - a0 += 2 * pi - # print("a0:%.4f part.a0:%.4f da:%.4f" % (a0, part.a0, da)) - part.a0 = a0 - - # adjust length of current segment + # adjust current segment w = g.segs[idx] part = d.parts[idx] - """ - w.p0 = pt - - if "C_" in part.type: - part.radius = w.r - else: - part.length = w.length - + if idx > 0: part.a0 = w.delta_angle(g.segs[idx - 1]) - - if idx + 1 < d.n_parts: - d.parts[idx + 1].a0 = w.delta_angle(g.segs[idx + 1]) - g = d.get_generator() - - """ - dp = w.p1 - pt - # adjust radius from distance between points.. - # use p0-p1 distance and angle as reference - if "C_" in part.type: - dw = w.p1 - w.p0 - part.radius = part.radius / dw.length * dp.length - da1 = atan2(dp.y, dp.x) - atan2(dw.y, dw.x) else: - part.length = dp.length - da1 = atan2(dp.y, dp.x) - w.straight(1).angle - - a0 = part.a0 + da1 - da - if a0 > pi: - a0 -= 2 * pi - if a0 < -pi: - a0 += 2 * pi - # print("a0:%.4f part.a0:%.4f da:%.4f" % (a0, part.a0, da)) - part.a0 = a0 - - # move object when point 0 - if idx == 0: + part.a0 = w.straight(1, 0).angle + # move object when point 0 self.o.location += sp.delta - """ - """ - # adjust rotation on both sides of next segment + + if "C_" in part.type: + part.radius = w.r + else: + part.length = w.length + + # adjust next one if idx + 1 < d.n_parts: - - part = d.parts[idx + 1] - a0 = part.a0 - da1 - if a0 > pi: - a0 -= 2 * pi - if a0 < -pi: - a0 += 2 * pi - part.a0 = a0 - - # refresh wall data for next loop - g = d.get_generator() - + d.parts[idx + 1].a0 = g.segs[idx + 1].delta_angle(w) + idx += 1 self.mouse_release(context, event) @@ -2111,9 +2061,9 @@ def manipulable_setup(self, context): self.manip_stack.append(m.setup(context, o, self)) def _manipulable_invoke(self, context): - + ArchipackStore.manipulable = self - + # take care of context switching # when call from outside of 3d view if context.space_data.type == 'VIEW_3D': @@ -2146,7 +2096,7 @@ def manipulable_invoke(self, context): return False else: bpy.ops.archipack.disable_manipulate() - + self.manip_stack = [] # kills other's manipulators self.manipulate_mode = True @@ -2194,7 +2144,7 @@ def manipulable_modal(self, context, event): # to delete / duplicate / link duplicate / unlink of # a complete set of wall, doors and windows at once self.manipulable_disable(context) - + if bpy.ops.object.delete.poll(): bpy.ops.object.delete('INVOKE_DEFAULT', use_global=False) @@ -2283,7 +2233,7 @@ def manipulable_modal(self, context, event): manipulator.select(self.manipulable_area) # keep focus to prevent left select mouse to actually move object return {'RUNNING_MODAL'} - + # event.alt here to prevent 3 button mouse emulation exit while zooming if event.type in {'RIGHTMOUSE', 'ESC'} and event.value == 'PRESS' and not event.alt: self.manipulable_disable(context) diff --git a/archipack_object.py b/archipack_object.py index 728037c..0d371e0 100644 --- a/archipack_object.py +++ b/archipack_object.py @@ -78,25 +78,44 @@ def datablock(cls, o): pass return None - def find_in_selection(self, context): + def find_in_selection(self, context, auto_update=True): """ find witch selected object this datablock instance belongs to + store context to be able to restore after oops provide support for "copy to selected" return - active: active_object - selected: selected_objects object or None when instance not found in selected objects """ + if not auto_update: + return None + active = context.active_object selected = [o for o in context.selected_objects] - + for o in selected: if self.__class__.datablock(o) == self: - return active, selected, o - - return active, selected, None - + self.previously_selected = selected + self.previously_active = active + return o + + return None + + def restore_context(self, context): + # restore context + bpy.ops.object.select_all(action="DESELECT") + + try: + for o in self.previously_selected: + o.select = True + except: + pass + self.previously_active.select = True + context.scene.objects.active = self.previously_active + self.previously_selected = None + self.previously_active = None + + class ArchipackCreateTool(): """ Shared property of archipack's create tool Operator diff --git a/archipack_reference_point.py b/archipack_reference_point.py index d9c0afc..d8910bd 100644 --- a/archipack_reference_point.py +++ b/archipack_reference_point.py @@ -200,7 +200,7 @@ class ARCHIPACK_OT_store_2d_reference(Operator): def poll(cls, context): return archipack_reference_point.filter(context.active_object) - def execute(self, context): +def execute(self, context): if context.mode == "OBJECT": o = context.active_object props = archipack_reference_point.datablock(o) @@ -260,8 +260,9 @@ def execute(self, context): props = archipack_reference_point.datablock(o) if props is None: return {'CANCELLED'} - sel = [obj for obj in context.selected_objects if obj != o] + sel = [obj for obj in context.selected_objects if obj != o and obj.parent != o] itM = o.matrix_world.inverted() + # print("parent_to_reference parenting:%s objects" % (len(sel))) for child in sel: rs = child.matrix_world.to_3x3().to_4x4() loc = itM * child.matrix_world.translation diff --git a/archipack_slab.py b/archipack_slab.py index a9a9b5f..a2abb91 100644 --- a/archipack_slab.py +++ b/archipack_slab.py @@ -45,6 +45,7 @@ class Slab(): def __init__(self): + # self.colour_inactive = (1, 1, 1, 1) pass def set_offset(self, offset, last=None): @@ -70,15 +71,15 @@ def curved_slab(self, a0, da, radius): class StraightSlab(Slab, Line): def __init__(self, p, v): - Slab.__init__(self) Line.__init__(self, p, v) + Slab.__init__(self) class CurvedSlab(Slab, Arc): def __init__(self, c, radius, a0, da): - Slab.__init__(self) Arc.__init__(self, c, radius, a0, da) + Slab.__init__(self) class SlabGenerator(): @@ -110,21 +111,47 @@ def add_part(self, part): 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: + return + """ + + 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]) + w = self.segs[-1] p1 = self.segs[0].line.p1 self.segs[0].line = self.segs[0].make_offset(self.parts[0].offset, w.line) self.segs[0].line.p1 = p1 - return def locate_manipulators(self): """ @@ -175,6 +202,42 @@ def get_verts(self, verts): 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 update(self, context): self.update(context) @@ -254,6 +317,7 @@ def update_type(self, context): if part == self: idx = i break + part = d.parts[idx] a0 = 0 if idx > 0: @@ -352,6 +416,7 @@ class ArchipackSegment(): unit='LENGTH', subtype='DISTANCE', update=update ) + linked_idx = IntProperty(default=-1) # @TODO: # flag to handle wall's x_offset @@ -391,6 +456,7 @@ def draw(self, context, layout, index): row.prop(self, "a0") row = box.row() row.prop(self, "offset") + # row.prop(self, "linked_idx") class archipack_slab_part(ArchipackSegment, PropertyGroup): @@ -448,11 +514,16 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup): unit='LENGTH', subtype='DISTANCE', update=update ) - + auto_synch = BoolProperty( + name="AutoSynch", + description="Keep wall in synch when editing", + default=True, + update=update_manipulators + ) # @TODO: # Global slab offset # will only affect slab parts sharing a wall - + childs = CollectionProperty(type=archipack_slab_child) # Flag to prevent mesh update while making bulk changes over variables # use : @@ -600,11 +671,7 @@ def setup_childs(self, o, g): """ # print("setup_childs") self.childs.clear() - if o.parent is not None: - otM = o.parent.matrix_world - else: - otM = Matrix() - itM = o.matrix_world.inverted() * otM + itM = o.matrix_world.inverted() dmax = 0.2 for c in o.children: @@ -621,18 +688,141 @@ def setup_childs(self, o, g): print("%s %s %s %s" % (idx, dist, d, c.name)) self.add_child(c.name, idx) + # synch wall + # store index of segments with p0 match + if self.auto_synch: + + if o.parent is not None: + + for i, part in enumerate(self.parts): + part.linked_idx = -1 + + d = None + for c in o.parent.children: + if c.data and "archipack_wall2" in c.data: + d = c.data.archipack_wall2[0] + break + if d is not None: + og = d.get_generator() + j = 0 + for i, part in enumerate(self.parts): + ji = j + while ji < d.n_parts + 1: + if (g.segs[i].p0 - og.segs[ji].p0).length < 0.005: + j = ji + 1 + part.linked_idx = ji + print("link: %s to %s" % (i, ji)) + break + ji += 1 + def relocate_childs(self, context, o, g): """ Move and resize childs after edition """ # print("relocate_childs") - tM = o.matrix_world + # Wall child syncro + # must store - idx of shared segs + # -> store this in parts provide 1:1 map + # share type: full, start only, end only + # -> may compute on the fly with idx stored + # when full segment does match + # -update type, radius, length, a0, and da + # when start only does match + # -update type, radius, a0 + # when end only does match + # -compute length/radius + # @TODO: + # handle p0 and p1 changes right in Generator (archipack_2d) + # and retrieve params from there + if self.auto_synch: + if o.parent is not None: + wall = None + + for child in o.parent.children: + if child.data and "archipack_wall2" in child.data: + wall = child + break + + if wall is not None: + d = wall.data.archipack_wall2[0] + d.auto_update = False + w = d.get_generator() + + last_idx = -1 + + # update og from g + for i, part in enumerate(self.parts): + idx = part.linked_idx + seg = g.segs[i] + + if i + 1 < self.n_parts: + next_idx = self.parts[i + 1].linked_idx + elif d.closed: + next_idx = self.parts[0].linked_idx + else: + next_idx = -1 + + if idx > -1: + + # start and shared: update rotation + a = seg.angle - w.segs[idx].angle + if abs(a) > 0.00001: + w.rotate(idx, a) + + if last_idx > -1: + w.segs[last_idx].p1 = seg.p0 + + if next_idx > -1: + + if idx + 1 == next_idx: + # shared: should move last point + # and apply to next segments + # this is overriden for common segs + # but translate non common ones + dp = seg.p1 - w.segs[idx].p1 + w.translate(idx, dp) + + # shared: transfert type too + if "C_" in part.type: + d.parts[idx].type = 'C_WALL' + w.segs[idx] = CurvedSlab(seg.c, seg.r, seg.a0, seg.da) + else: + d.parts[idx].type = 'S_WALL' + w.segs[idx] = StraightSlab(seg.p.copy(), seg.v.copy()) + last_idx = -1 + + elif next_idx > -1: + # only last is shared + # note: on next run will be part of start + last_idx = next_idx - 1 + + # update d from og + for i, seg in enumerate(w.segs): + if i > 0: + d.parts[i].a0 = seg.delta_angle(w.segs[i - 1]) + else: + d.parts[i].a0 = seg.angle + if "C_" in d.parts[i].type: + d.parts[i].radius = seg.r + d.parts[i].da = seg.da + else: + d.parts[i].length = max(0.01, seg.length) + + wall.select = True + context.scene.objects.active = wall + + d.auto_update = True + wall.select = False + o.select = True + context.scene.objects.active = o + + wall.matrix_world = o.matrix_world.copy() + tM = o.matrix_world for child in self.childs: c, d = child.get_child(context) if c is None: - print("c is None") continue a = g.segs[child.idx].angle @@ -675,33 +865,23 @@ def remove_part(self, context, where): g = self.get_generator() w = g.segs[where - 1] - dp = g.segs[where].p1 - w.p0 + w.p1 = g.segs[where].p1 + if where + 1 < self.n_parts: - a0 = g.segs[where + 1].straight(1).angle - atan2(dp.y, dp.x) - part = self.parts[where + 1] - if a0 > pi: - a0 -= 2 * pi - if a0 < -pi: - a0 += 2 * pi - part.a0 = a0 + self.parts[where + 1].a0 = g.segs[where + 1].delta_angle(w) + part = self.parts[where - 1] - # adjust radius from distance between points.. - # use p0-p1 distance as reference + if "C_" in part.type: - dw = (w.p1 - w.p0) - part.radius = part.radius / dw.length * dp.length - # angle pt - p0 - angle p0 p1 - da = atan2(dp.y, dp.x) - atan2(dw.y, dw.x) + part.radius = w.r else: - part.length = dp.length - da = atan2(dp.y, dp.x) - w.straight(1).angle - a0 = part.a0 + da - if a0 > pi: - a0 -= 2 * pi - if a0 < -pi: - a0 += 2 * pi - # print("a0:%.4f part.a0:%.4f da:%.4f" % (a0, part.a0, da)) - part.a0 = a0 + part.length = w.length + + if where > 1: + part.a0 = w.delta_angle(g.segs[where - 2]) + else: + part.a0 = w.straight(1, 0).angle + for c in self.childs: if c.idx >= where: c.idx -= 1 @@ -818,12 +998,17 @@ def from_spline(self, wM, resolution, spline): else: pts.append(wM * points[-1].co) + self.from_points(pts, spline.use_cyclic_u) + + def from_points(self, pts, closed): + if self.is_cw(pts): pts = list(reversed(pts)) self.auto_update = False self.n_parts = len(pts) - 1 + self.update_parts(None) p0 = pts.pop(0) @@ -835,13 +1020,16 @@ def from_spline(self, wM, resolution, spline): 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.closed = closed self.auto_update = True def make_surface(self, o, verts): @@ -873,9 +1061,9 @@ def unwrap_uv(self, o): def update(self, context, manipulable_refresh=False, update_childs=False): - active, selected, o = self.find_in_selection(context) + o = self.find_in_selection(context, self.auto_update) - if o is None or not self.auto_update: + if o is None: return # clean up manipulators before any data model change @@ -917,14 +1105,7 @@ def update(self, context, manipulable_refresh=False, update_childs=False): self.manipulable_refresh = True # restore context - try: - for o in selected: - o.select = True - except: - pass - - active.select = True - context.scene.objects.active = active + self.restore_context(context) def manipulable_setup(self, context): """ @@ -1008,6 +1189,8 @@ def draw(self, context): box = layout.box() box.prop(prop, 'z') box = layout.box() + box.prop(prop, 'auto_synch') + box = layout.box() row = box.row() if prop.parts_expand: row.prop(prop, 'parts_expand', icon="TRIA_DOWN", icon_only=True, text="Parts", emboss=False) @@ -1201,7 +1384,7 @@ def poll(self, context): def create(self, context): wall = context.active_object wd = wall.data.archipack_wall2[0] - bpy.ops.archipack.slab(auto_manipulate=self.auto_manipulate) + bpy.ops.archipack.slab(auto_manipulate=False) o = context.scene.objects.active d = archipack_slab.datablock(o) d.auto_update = False @@ -1229,6 +1412,22 @@ def create(self, context): ]) * wall.matrix_world else: o.matrix_world = wall.matrix_world.copy() + bpy.ops.object.select_all(action='DESELECT') + # parenting childs to wall reference point + if wall.parent is None: + x, y, z = wall.bound_box[0] + context.scene.cursor_location = wall.matrix_world * Vector((x, y, z)) + # fix issue #9 + context.scene.objects.active = wall + bpy.ops.archipack.reference_point() + else: + wall.parent.select = True + context.scene.objects.active = wall.parent + wall.select = True + o.select = True + bpy.ops.archipack.parent_to_reference() + wall.parent.select = False + return o # ----------------------------------------------------- @@ -1237,7 +1436,11 @@ def create(self, context): def execute(self, context): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") - self.create(context) + o = self.create(context) + o.select = True + context.scene.objects.active = o + if self.auto_manipulate: + bpy.ops.archipack.slab_manipulate('INVOKE_DEFAULT') return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") diff --git a/archipack_stair.py b/archipack_stair.py index b24b93e..1a7ff3c 100644 --- a/archipack_stair.py +++ b/archipack_stair.py @@ -1548,7 +1548,7 @@ class archipack_stair_material(PropertyGroup): update=update ) - def find_in_selection(self, context): + def find_datablock_in_selection(self, context): """ find witch selected object this instance belongs to provide support for "copy to selected" @@ -1563,7 +1563,7 @@ def find_in_selection(self, context): return None def update(self, context): - props = self.find_in_selection(context) + props = self.find_datablock_in_selection(context) if props is not None: props.update(context) @@ -1621,7 +1621,7 @@ class archipack_stair_part(PropertyGroup): ) manipulators = CollectionProperty(type=archipack_manipulator) - def find_in_selection(self, context): + def find_datablock_in_selection(self, context): """ find witch selected object this instance belongs to provide support for "copy to selected" @@ -1636,7 +1636,7 @@ def find_in_selection(self, context): return None def update(self, context, manipulable_refresh=False): - props = self.find_in_selection(context) + props = self.find_datablock_in_selection(context) if props is not None: props.update(context, manipulable_refresh) @@ -2298,9 +2298,9 @@ def update_parts(self): def update(self, context, manipulable_refresh=False): - active, selected, o = self.find_in_selection(context) + o = self.find_in_selection(context, self.auto_update) - if o is None or not self.auto_update: + if o is None: return # clean up manipulators before any data model change @@ -2492,24 +2492,12 @@ def update(self, context, manipulable_refresh=False): self.string_alt, 0, verts, faces, matids, uvs) bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs, weld=True, clean=True) - bpy.ops.object.shade_smooth() # enable manipulators rebuild if manipulable_refresh: self.manipulable_refresh = True - # bpy.ops.mesh.select_linked() - # bpy.ops.mesh.faces_shade_smooth() - - # restore context - try: - for o in selected: - o.select = True - except: - pass - - active.select = True - context.scene.objects.active = active + self.restore_context(context) def manipulable_setup(self, context): """ @@ -2549,6 +2537,10 @@ class ARCHIPACK_PT_stair(Panel): # bl_context = 'object' bl_category = 'ArchiPack' + @classmethod + def poll(cls, context): + return archipack_stair.filter(context.active_object) + def draw(self, context): prop = archipack_stair.datablock(context.active_object) if prop is None: @@ -2740,9 +2732,6 @@ def draw(self, context): else: row.prop(prop, 'idmats_expand', icon="TRIA_RIGHT", icon_only=True, text="Materials", emboss=False) - @classmethod - def poll(cls, context): - return archipack_stair.filter(context.active_object) # ------------------------------------------------------------------ # Define operator class to create object @@ -2765,9 +2754,7 @@ def create(self, context): context.scene.objects.active = o self.load_preset(d) self.add_material(o) - # auto smooth arround 12 deg fix Tynkatopi smoothing issue ;) m.auto_smooth_angle = 0.20944 - m.use_auto_smooth = True return o # ----------------------------------------------------- @@ -2839,7 +2826,6 @@ def register(): bpy.utils.register_class(archipack_stair_part) bpy.utils.register_class(archipack_stair) Mesh.archipack_stair = CollectionProperty(type=archipack_stair) - # bpy.utils.register_class(ARCHIPACK_MT_stair_preset) bpy.utils.register_class(ARCHIPACK_PT_stair) bpy.utils.register_class(ARCHIPACK_OT_stair) bpy.utils.register_class(ARCHIPACK_OT_stair_preset_menu) @@ -2852,7 +2838,6 @@ def unregister(): bpy.utils.unregister_class(archipack_stair_part) bpy.utils.unregister_class(archipack_stair) del Mesh.archipack_stair - # bpy.utils.unregister_class(ARCHIPACK_MT_stair_preset) bpy.utils.unregister_class(ARCHIPACK_PT_stair) bpy.utils.unregister_class(ARCHIPACK_OT_stair) bpy.utils.unregister_class(ARCHIPACK_OT_stair_preset_menu) diff --git a/archipack_toolkit.py b/archipack_toolkit.py new file mode 100644 index 0000000..77f6299 --- /dev/null +++ b/archipack_toolkit.py @@ -0,0 +1,325 @@ +# -*- 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) +# +# ---------------------------------------------------------- +import bpy +from bpy.types import Operator, PropertyGroup, Mesh, Panel + +# Minimal required property types +from bpy.props import ( + FloatProperty, BoolProperty, CollectionProperty + ) +from mathutils import Vector +# Bmesh made easy +from .bmesh_utils import BmeshEdit as bmed + +# Manipulable +from .archipack_manipulator import Manipulable + +# Preset system +from .archipack_preset import ArchipackPreset, PresetMenuOperator + +# Base for Propertygroup and create tool Operator +from .archipack_object import ArchipackCreateTool, ArchipackObject + + +def update(self, context): + self.update(context) + + +class archipack_myobject(ArchipackObject, Manipulable, PropertyGroup): + """ Archipack toolkit sample""" + x = FloatProperty( + name="Width", + default=2.0, min=0.01, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + y = FloatProperty( + name="Depth", + default=2.0, min=0.01, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + z = FloatProperty( + name="Height", + default=2.0, min=0.01, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + auto_update = BoolProperty( + # Wont save auto_update state in any case + options={'SKIP_SAVE'}, + default=True, + update=update + ) + + @property + def verts(self): + """ + Object vertices coords + """ + x = 0.5 * self.x + y = 0.5 * self.y + z = self.z + return [ + (-x, y, 0), + (-x, -y, 0), + (x, -y, 0), + (x, y, 0), + (-x, y, z), + (-x, -y, z), + (x, -y, z), + (x, y, z) + ] + + @property + def faces(self): + """ + Object faces vertices index + """ + return [ + (0, 1, 2, 3), + (7, 6, 5, 4), + (7, 4, 0, 3), + (4, 5, 1, 0), + (5, 6, 2, 1), + (6, 7, 3, 2) + ] + + @property + def uvs(self): + """ + Object faces uv coords + """ + return [ + [(0, 0), (0, 1), (1, 1), (1, 0)], + [(0, 0), (0, 1), (1, 1), (1, 0)], + [(0, 0), (0, 1), (1, 1), (1, 0)], + [(0, 0), (0, 1), (1, 1), (1, 0)], + [(0, 0), (0, 1), (1, 1), (1, 0)], + [(0, 0), (0, 1), (1, 1), (1, 0)] + ] + + @property + def matids(self): + """ + Object material indexes for each face + """ + return [0, 0, 0, 0, 0, 0] + + def setup_manipulators(self): + if len(self.manipulators) < 1: + # add manipulator for x property + s = self.manipulators.add() + s.prop1_name = "x" + s.type_key = 'SIZE' + + # add manipulator for y property + s = self.manipulators.add() + s.prop1_name = "y" + s.type_key = 'SIZE' + + # add manipulator for z property + s = self.manipulators.add() + s.prop1_name = "z" + s.type_key = 'SIZE' + # draw this one on xz plane + s.normal = Vector((0, 1, 0)) + + def update(self, context): + + # provide support for "copy to selected" + o = self.find_in_selection(context, self.auto_update) + + if o is None: + return + + # dynamically create manipulators when needed + self.setup_manipulators() + + # update your mesh from parameters + bmed.buildmesh(context, + o, + self.verts, + self.faces, + matids=self.matids, + uvs=self.uvs, + weld=False) + + # update manipulators location (3d location in object coordsystem) + x, y = 0.5 * self.x, 0.5 * self.y + self.manipulators[0].set_pts([(-x, -y, 0), (x, -y, 0), (1, 0, 0)]) + self.manipulators[1].set_pts([(-x, -y, 0), (-x, y, 0), (-1, 0, 0)]) + self.manipulators[2].set_pts([(x, -y, 0), (x, -y, self.z), (-1, 0, 0)]) + + # always restore context + self.restore_context() + + +class ARCHIPACK_PT_myobject(Panel): + bl_idname = "ARCHIPACK_PT_myobject" + bl_label = "MyObject" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'ArchiPack' + + @classmethod + def poll(cls, context): + # ensure your object panel only show when active object is the right one + return archipack_myobject.filter(context.active_object) + + def draw(self, context): + o = context.active_object + if not archipack_myobject.filter(o): + return + layout = self.layout + + # retrieve datablock of your object + props = archipack_myobject.datablock(o) + + # Manipulate mode operator + layout.operator('archipack.myobject_manipulate', icon='HAND') + + box = layout.box() + row = box.row(align=True) + + # Presets operators + row.operator("archipack.myobject_preset_menu", + text=bpy.types.ARCHIPACK_OT_myobject_preset_menu.bl_label) + row.operator("archipack.myobject_preset", + text="", + icon='ZOOMIN') + row.operator("archipack.myobject_preset", + text="", + icon='ZOOMOUT').remove_active = True + + row = layout.row() + box = row.box() + box.label(text="Size") + box.prop(props, 'x') + box.prop(props, 'y') + box.prop(props, 'z') + + +class ARCHIPACK_OT_myobject(ArchipackCreateTool, Operator): + bl_idname = "archipack.myobject" + bl_label = "Myobject" + bl_description = "Create Myobject" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + def create(self, context): + + # Create an empty mesh datablock + m = bpy.data.meshes.new("Myobject") + + # Create an object using the mesh datablock + o = bpy.data.objects.new("Myobject", m) + + # Add your properties on mesh datablock + d = m.archipack_myobject.add() + + # Link object into scene + 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 + + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.location = bpy.context.scene.cursor_location + o.select = True + context.scene.objects.active = o + + # Start manipulate mode + self.manipulate() + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_myobject_preset_menu(PresetMenuOperator, Operator): + bl_idname = "archipack.myobject_preset_menu" + bl_label = "Myobject preset" + preset_subdir = "archipack_myobject" + + +class ARCHIPACK_OT_myobject_preset(ArchipackPreset, Operator): + """Add a Myobject Preset""" + bl_idname = "archipack.myobject_preset" + bl_label = "Add Myobject preset" + preset_menu = "ARCHIPACK_OT_myobject_preset_menu" + + @property + def blacklist(self): + return ['manipulators'] + + +class ARCHIPACK_OT_myobject_manipulate(Operator): + bl_idname = "archipack.myobject_manipulate" + bl_label = "Manipulate" + bl_description = "Manipulate" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_myobject.filter(context.active_object) + + def invoke(self, context, event): + d = archipack_myobject.datablock(context.active_object) + d.manipulable_invoke(context) + return {'FINISHED'} + + +def register(): + bpy.utils.register_class(archipack_myobject) + Mesh.archipack_myobject = CollectionProperty(type=archipack_myobject) + bpy.utils.register_class(ARCHIPACK_PT_myobject) + bpy.utils.register_class(ARCHIPACK_OT_myobject) + bpy.utils.register_class(ARCHIPACK_OT_myobject_preset_menu) + bpy.utils.register_class(ARCHIPACK_OT_myobject_preset) + bpy.utils.register_class(ARCHIPACK_OT_myobject_manipulate) + + +def unregister(): + bpy.utils.unregister_class(archipack_myobject) + del Mesh.archipack_myobject + bpy.utils.unregister_class(ARCHIPACK_PT_myobject) + bpy.utils.unregister_class(ARCHIPACK_OT_myobject) + bpy.utils.unregister_class(ARCHIPACK_OT_myobject_preset_menu) + bpy.utils.unregister_class(ARCHIPACK_OT_myobject_preset) + bpy.utils.unregister_class(ARCHIPACK_OT_myobject_manipulate) diff --git a/archipack_truss.py b/archipack_truss.py index 00a8b1f..0af7d13 100644 --- a/archipack_truss.py +++ b/archipack_truss.py @@ -140,9 +140,9 @@ def docylinder(self, faces, verts, radius, segs, tMt, tMb, tM, add=False): def update(self, context): - active, selected, o = self.find_in_selection(context) + o = self.find_in_selection(context, self.auto_update) - if o is None or not self.auto_update: + if o is None: return self.setup_manipulators() @@ -270,17 +270,8 @@ def update(self, context): bmed.buildmesh(context, o, verts, faces, matids=None, uvs=None, weld=False) self.manipulators[0].set_pts([(0, 0, 0), (0, 0, self.z), (1, 0, 0)]) - bpy.ops.object.shade_smooth() - # restore context - try: - for o in selected: - o.select = True - except: - pass - - active.select = True - context.scene.objects.active = active + self.restore_context(context) class ARCHIPACK_PT_truss(Panel): @@ -291,6 +282,10 @@ class ARCHIPACK_PT_truss(Panel): bl_region_type = 'UI' bl_category = 'ArchiPack' + @classmethod + def poll(cls, context): + return archipack_truss.filter(context.active_object) + def draw(self, context): prop = archipack_truss.datablock(context.active_object) if prop is None: @@ -309,10 +304,6 @@ def draw(self, context): box.prop(prop, 'slaves_radius') box.prop(prop, 'entre_axe') - @classmethod - def poll(cls, context): - return archipack_truss.filter(context.active_object) - class ARCHIPACK_OT_truss(ArchipackCreateTool, Operator): bl_idname = "archipack.truss" @@ -333,8 +324,6 @@ def create(self, context): self.load_preset(d) self.add_material(o) m.auto_smooth_angle = 1.15 - m.use_auto_smooth = True - # MaterialUtils.add_wall_materials(o) return o # ----------------------------------------------------- diff --git a/archipack_wall.py b/archipack_wall.py index 86532f9..b2164e3 100644 --- a/archipack_wall.py +++ b/archipack_wall.py @@ -28,13 +28,14 @@ import bmesh from bpy.types import Operator, PropertyGroup, Mesh, Panel from bpy.props import FloatProperty, CollectionProperty +from .archipack_object import ArchipackObject def update_wall(self, context): self.update(context) -class archipack_wall(PropertyGroup): +class archipack_wall(ArchipackObject, PropertyGroup): z = FloatProperty( name='height', min=0.1, max=10000, @@ -47,7 +48,7 @@ def update(self, context): # this should be the rule for other simple objects # as long as there is no topologic changes o = context.active_object - if ARCHIPACK_PT_wall.params(o) != self: + if archipack_wall.datablock(o) != self: return bpy.ops.object.mode_set(mode='EDIT') me = o.data @@ -71,40 +72,18 @@ class ARCHIPACK_PT_wall(Panel): bl_region_type = 'UI' bl_category = 'ArchiPack' + @classmethod + def poll(cls, context): + return archipack_wall.filter(context.active_object) + def draw(self, context): - o = context.object - if not ARCHIPACK_PT_wall.filter(o): + + prop = archipack_wall.datablock(context.active_object) + if prop is None: return layout = self.layout - prop = o.data.archipack_wall[0] layout.prop(prop, 'z') - @classmethod - def params(cls, o): - try: - if 'archipack_wall' not in o.data: - return False - else: - return o.data.archipack_wall[0] - except: - return False - - @classmethod - def filter(cls, o): - try: - if 'archipack_wall' not in o.data: - return False - else: - return True - except: - return False - - @classmethod - def poll(cls, context): - o = context.object - if o is None: - return False - return cls.filter(o) # ------------------------------------------------------------------ # Define operator class to create object @@ -134,7 +113,7 @@ def draw(self, context): def execute(self, context): if context.mode == "OBJECT": o = context.active_object - if ARCHIPACK_PT_wall.filter(o): + if archipack_wall.filter(o): return {'CANCELLED'} params = o.data.archipack_wall.add() params.z = self.z diff --git a/archipack_wall2.py b/archipack_wall2.py index 27398ca..61f8187 100644 --- a/archipack_wall2.py +++ b/archipack_wall2.py @@ -54,7 +54,7 @@ class Wall(): - def __init__(self, last, wall_z, z, t, flip): + def __init__(self, wall_z, z, t, flip): self.z = z self.wall_z = wall_z self.t = t @@ -95,7 +95,7 @@ def make_wall(self, i, verts, faces): def straight_wall(self, a0, length, wall_z, z, t): r = self.straight(length).rotate(a0) - return StraightWall(self, r.p, r.v, wall_z, z, t, self.flip) + return StraightWall(r.p, r.v, wall_z, z, t, self.flip) def curved_wall(self, a0, da, radius, wall_z, z, t): n = self.normal(1).rotate(a0).scale(radius) @@ -103,13 +103,13 @@ def curved_wall(self, a0, da, radius, wall_z, z, t): n.v = -n.v a0 = n.angle c = n.p - n.v - return CurvedWall(self, c, radius, a0, da, wall_z, z, t, self.flip) + return CurvedWall(c, radius, a0, da, wall_z, z, t, self.flip) class StraightWall(Wall, Line): - def __init__(self, last, p, v, wall_z, z, t, flip): + def __init__(self, p, v, wall_z, z, t, flip): Line.__init__(self, p, v) - Wall.__init__(self, last, wall_z, z, t, flip) + Wall.__init__(self, wall_z, z, t, flip) def param_t(self, step_angle): self.t_step = self.t @@ -117,9 +117,9 @@ def param_t(self, step_angle): class CurvedWall(Wall, Arc): - def __init__(self, last, c, radius, a0, da, wall_z, z, t, flip): + def __init__(self, c, radius, a0, da, wall_z, z, t, flip): Arc.__init__(self, c, radius, a0, da) - Wall.__init__(self, last, wall_z, z, t, flip) + Wall.__init__(self, wall_z, z, t, flip) def param_t(self, step_angle): t_step, n_step = self.steps_by_angle(step_angle) @@ -175,10 +175,10 @@ def add_part(self, part, wall_z, flip): if part.type == 'S_WALL': p = Vector((0, 0)) v = part.length * Vector((cos(part.a0), sin(part.a0))) - s = StraightWall(s, p, v, wall_z, z, t, flip) + s = StraightWall(p, v, wall_z, z, t, flip) elif part.type == 'C_WALL': c = -part.radius * Vector((cos(part.a0), sin(part.a0))) - s = CurvedWall(s, c, part.radius, part.a0, part.da, wall_z, z, t, flip) + s = CurvedWall(c, part.radius, part.a0, part.da, wall_z, z, t, flip) else: if part.type == 'S_WALL': s = s.straight_wall(part.a0, part.length, wall_z, z, t) @@ -274,7 +274,38 @@ def make_wall(self, step_angle, flip, closed, verts, faces): for j in range(wall.n_step): print("%s" % (wall.n_step)) # wall.make_wall(j, verts, faces) - + + def rotate(self, idx_from, a): + """ + apply rotation to all following segs + """ + self.segs[idx_from].rotate(a) + ca = cos(a) + sa = sin(a) + rM = Matrix([ + [ca, -sa], + [sa, ca] + ]) + # rotation center + p0 = self.segs[idx_from].p0 + for i in range(idx_from + 1, len(self.segs)): + seg = self.segs[i] + seg.rotate(a) + dp = rM * (seg.p0 - p0) + seg.translate(dp) + + def translate(self, idx_from, dp): + """ + apply translation to all following segs + """ + self.segs[idx_from].p1 += dp + for i in range(idx_from + 1, len(self.segs)): + self.segs[i].translate(dp) + + def draw(self, context): + for seg in self.segs: + seg.draw(context, render=False) + def debug(self, verts): for wall in self.segs: for i in range(33): @@ -309,7 +340,7 @@ def get_splits(self): def update_type(self, context): - d = self.find_in_selection(context) + d = self.find_datablock_in_selection(context) if d is not None and d.auto_update: @@ -453,7 +484,7 @@ def _set_t(self, splits): for i in range(splits): self.t[i] = t - def find_in_selection(self, context): + def find_datablock_in_selection(self, context): """ find witch selected object this instance belongs to provide support for "copy to selected" @@ -470,7 +501,7 @@ def find_in_selection(self, context): def update(self, context, manipulable_refresh=False): if not self.auto_update: return - props = self.find_in_selection(context) + props = self.find_datablock_in_selection(context) if props is not None: props.update(context, manipulable_refresh) @@ -597,6 +628,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): ) # dumb manipulators to show sizes between childs childs_manipulators = CollectionProperty(type=archipack_manipulator) + # store to manipulate windows and doors childs = CollectionProperty(type=archipack_wall2_child) def insert_part(self, context, where): @@ -637,34 +669,23 @@ def remove_part(self, context, where): if where > 0: g = self.get_generator() w = g.segs[where - 1] - dp = g.segs[where].p1 - w.p0 + w.p1 = g.segs[where].p1 + if where + 1 < self.n_parts: - a0 = g.segs[where + 1].straight(1).angle - atan2(dp.y, dp.x) - part = self.parts[where + 1] - if a0 > pi: - a0 -= 2 * pi - if a0 < -pi: - a0 += 2 * pi - part.a0 = a0 + self.parts[where + 1].a0 = g.segs[where + 1].delta_angle(w) + part = self.parts[where - 1] - # adjust radius from distance between points.. - # use p0-p1 distance as reference + if "C_" in part.type: - dw = (w.p1 - w.p0) - part.radius = part.radius / dw.length * dp.length - # angle pt - p0 - angle p0 p1 - da = atan2(dp.y, dp.x) - atan2(dw.y, dw.x) + part.radius = w.r else: - part.length = dp.length - da = atan2(dp.y, dp.x) - w.straight(1).angle - a0 = part.a0 + da - if a0 > pi: - a0 -= 2 * pi - if a0 < -pi: - a0 += 2 * pi - # print("a0:%.4f part.a0:%.4f da:%.4f" % (a0, part.a0, da)) - part.a0 = a0 - + part.length = w.length + + if where > 1: + part.a0 = w.delta_angle(g.segs[where - 2]) + else: + part.a0 = w.straight(1, 0).angle + self.parts.remove(where) self.n_parts -= 1 # fix snap manipulators index @@ -786,6 +807,10 @@ def from_spline(self, wM, resolution, spline): else: pts.append(wM * points[-1].co) + self.from_points(pts, spline.use_cyclic_u) + + def from_points(self, pts, closed): + if self.is_cw(pts): pts = list(reversed(pts)) @@ -793,7 +818,7 @@ def from_spline(self, wM, resolution, spline): self.n_parts = len(pts) - 1 - if spline.use_cyclic_u: + if closed: self.n_parts -= 1 self.update_parts(None) @@ -817,18 +842,12 @@ def from_spline(self, wM, resolution, spline): a0 += da p0 = p1 - self.closed = spline.use_cyclic_u + self.closed = closed self.auto_update = True def update(self, context, manipulable_refresh=False, update_childs=False): - # print("update manipulable_refresh:%s" % (manipulable_refresh)) - """ - - """ - if not self.auto_update: - return - - active, selected, o = self.find_in_selection(context) + + o = self.find_in_selection(context, self.auto_update) if o is None: return @@ -853,8 +872,7 @@ def update(self, context, manipulable_refresh=False, update_childs=False): # print("buildmesh") bmed.buildmesh(context, o, verts, faces, matids=None, uvs=None, weld=True) - bpy.ops.object.shade_smooth() - + side = 1 if self.flip: side = -1 @@ -902,16 +920,8 @@ def update(self, context, manipulable_refresh=False, update_childs=False): # print("manipulable_refresh=True") self.manipulable_refresh = True - # restore context - try: - for o in selected: - o.select = True - except: - pass - - active.select = True - context.scene.objects.active = active - + self.restore_context(context) + # manipulable children objects like windows and doors def child_partition(self, array, begin, end): pivot = begin @@ -975,7 +985,8 @@ def setup_childs(self, o, g): for child in o.parent.children: if (child != o and 'archipack_robusthole' not in child and - 'archipack_hole' not in child): + 'archipack_hole' not in child and + child.data and 'archipack_slab' not in child.data): tM = child.matrix_world.to_3x3() pt = (itM * child.location).to_2d() for wall_idx, wall in enumerate(g.segs): @@ -1259,6 +1270,7 @@ def manipulable_invoke(self, context): # Update throttle (smell hack here) # use 2 globals to store a timer and state of update_action +# NO MORE USING THIS PART, kept as it as it may be usefull in some cases update_timer = None update_timer_updating = False @@ -1366,12 +1378,12 @@ def create(self, context): d.manipulable_selectable = True context.scene.objects.link(o) o.select = True - context.scene.objects.active = o - self.load_preset(d) - self.add_material(o) # around 12 degree m.auto_smooth_angle = 0.20944 m.use_auto_smooth = True + context.scene.objects.active = o + self.load_preset(d) + self.add_material(o) return o def execute(self, context): @@ -1478,6 +1490,22 @@ def create(self, context): d.auto_update = True # pretranslate o.matrix_world = slab.matrix_world.copy() + + bpy.ops.object.select_all(action='DESELECT') + # parenting childs to wall reference point + if o.parent is None: + x, y, z = o.bound_box[0] + context.scene.cursor_location = o.matrix_world * Vector((x, y, z)) + # fix issue #9 + context.scene.objects.active = o + bpy.ops.archipack.reference_point() + else: + o.parent.select = True + context.scene.objects.active = o.parent + o.select = True + slab.select = True + bpy.ops.archipack.parent_to_reference() + o.parent.select = False return o # ----------------------------------------------------- @@ -1517,7 +1545,9 @@ class ARCHIPACK_OT_wall2_draw(Operator): label = None feedback = None takeloc = Vector((0, 0, 0)) - + sel = [] + act = None + def mouse_to_plane(self, context, event): """ convert mouse pos to 3d point over plane defined by origin and normal @@ -1632,56 +1662,28 @@ def sp_init(self, context, event, state, sp): def ensure_ccw(self): """ Wall to slab expect wall vertex order to be ccw - so reverse order here when needed + so reverse order here when needed """ d = archipack_wall2.datablock(self.o) g = d.get_generator() pts = [seg.p0.to_3d() for seg in g.segs] - + if d.closed: pts.append(pts[0]) - + if d.is_cw(pts): d.x_offset = 1 pts = list(reversed(pts)) - self.o.location += pts[0] - pts[-1] - - d.auto_update = False - - d.n_parts = len(pts) - 1 - - if d.closed: - d.n_parts -= 1 - - d.update_parts(None) + self.o.location += pts[0] - pts[-1] - 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(d.parts): - print("Too many pts for parts") - break - p = d.parts[i] - p.length = dp.to_2d().length - p.dz = dp.z - p.a0 = da - a0 += da - p0 = p1 + d.from_points(pts, d.closed) - d.auto_update = True - def modal(self, context, event): context.area.tag_redraw() # print("modal event %s %s" % (event.type, event.value)) - # if event.type == 'NONE': - # return {'PASS_THROUGH'} + if event.type == 'NONE': + return {'PASS_THROUGH'} if self.state == 'STARTING': takeloc = self.mouse_to_plane(context, event) @@ -1722,12 +1724,23 @@ def modal(self, context, event): context.scene.objects.active = o d = archipack_wall2.datablock(o) g = d.get_generator() - takeloc = o.matrix_world * g.segs[-2].p1.to_3d() + p0 = g.segs[-2].p0 + p1 = g.segs[-2].p1 + dp = p1-p0 + takemat = o.matrix_world * Matrix([ + [dp.x, dp.y, 0, p1.x], + [dp.y, -dp.x, 0, p1.y], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]) + takeloc = o.matrix_world * p1.to_3d() o.select = False else: takeloc = self.mouse_to_plane(context, event) - + takemat = None + snap_point(takeloc=takeloc, + takemat=takemat, draw=self.sp_draw, callback=self.sp_callback, constraint_axis=(True, True, False), @@ -1753,7 +1766,11 @@ def modal(self, context, event): self.feedback.disable() bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') - if self.o is not None: + if self.o is None: + context.scene.objects.active = self.act + for o in self.sel: + o.select = True + else: self.o.select = True context.scene.objects.active = self.o self.ensure_ccw() @@ -1783,7 +1800,11 @@ def invoke(self, context, event): ]) self.feedback.enable() args = (self, context) - + + self.sel = [o for o in context.selected_objects] + self.act = context.active_object + bpy.ops.object.select_all(action="DESELECT") + self.state = 'STARTING' self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback, args, 'WINDOW', 'POST_PIXEL') diff --git a/archipack_window.py b/archipack_window.py index 8c64480..22a2fc5 100644 --- a/archipack_window.py +++ b/archipack_window.py @@ -149,14 +149,14 @@ def _set_width(self, cols): for i in range(cols - 1): self.width[i] = width - def find_in_selection(self, context): + def find_datablock_in_selection(self, context): """ find witch selected object this instance belongs to provide support for "copy to selected" """ selected = [o for o in context.selected_objects] for o in selected: - props = ARCHIPACK_PT_window.params(o) + props = archipack_window.datablock(o) if props: for row in props.rows: if row == self: @@ -165,7 +165,7 @@ def find_in_selection(self, context): def update(self, context): if self.auto_update: - props = self.find_in_selection(context) + props = self.find_datablock_in_selection(context) if props is not None: props.update(context, childs_only=False) @@ -185,7 +185,7 @@ def draw(self, layout, context, last_row): row.prop(self, "fixed", text="fixed", index=(self.cols - 1)) -class archipack_window_panel(PropertyGroup): +class archipack_window_panel(ArchipackObject, PropertyGroup): center = FloatVectorProperty( subtype='XYZ' ) @@ -360,19 +360,6 @@ def uvs(self): return self.window.uv(self.curve_steps, self.center, self.origin, self.size, self.radius, self.angle_y, self.pivot, 0, self.frame_x, path_type=self.shape) - def find_in_selection(self, context): - """ - find witch selected object this instance belongs to - provide support for "copy to selected" - """ - active = context.active_object - selected = [o for o in context.selected_objects] - for o in selected: - c, props = ARCHIPACK_PT_window_panel.params(o) - if props == self: - return active, selected, o - return active, selected, None - def find_handle(self, o): for child in o.children: if 'archipack_handle' in child: @@ -399,7 +386,9 @@ def remove_handle(self, context, o): bpy.data.objects.remove(handle, do_unlink=True) def update(self, context): - active, selected, o = self.find_in_selection(context) + + o = self.find_in_selection(context) + if o is None: return @@ -411,8 +400,7 @@ def update(self, context): bmed.buildmesh(context, o, self.verts, self.faces, self.matids, self.uvs) - active.select = True - context.scene.objects.active = active + self.restore_context(context) class archipack_window(ArchipackObject, Manipulable, PropertyGroup): @@ -992,7 +980,7 @@ def remove_childs(self, context, o, to_remove): for child in o.children: if to_remove < 1: return - if ARCHIPACK_PT_window_panel.filter(child): + if archipack_window_panel.filter(child): to_remove -= 1 self.remove_handle(context, child) context.scene.objects.unlink(child) @@ -1030,13 +1018,8 @@ def update_rows(self, context, o): self.remove_childs(context, o, n_childs - w_childs) def get_childs_panels(self, context, o): - childs = [] - for child in o.children: - c, props = ARCHIPACK_PT_window_panel.params(child) - if props is not None: - childs.append(c) - return childs - + return [child for child in o.children if archipack_window_panel.filter(child)] + def adjust_size_and_origin(self, size, origin, pivot, materials): if len(size) > 1: size[0].x += 0.5 * self.frame_x @@ -1228,7 +1211,7 @@ def update_childs(self, context, o): child = childs[child_n - 1] child.select = True context.scene.objects.active = child - c, props = ARCHIPACK_PT_window_panel.params(child) + props = archipack_window_panel.datablock(child) if props is not None: props.origin = Vector((origin[panel].x, offset.y, 0)) props.center = center @@ -1329,13 +1312,11 @@ def get_radius(self): def update(self, context, childs_only=False): # support for "copy to selected" - active, selected, o = self.find_in_selection(context) + o = self.find_in_selection(context, self.auto_update) - if o is None or not self.auto_update: + if o is None: return - # print("Window.update()") - self.setup_manipulators() if childs_only is False: @@ -1358,16 +1339,7 @@ def update(self, context, childs_only=False): self.manipulators[3].set_pts([(x, -y, 0), (x, -y, self.altitude), (-1, 0, 0)]) # restore context - bpy.ops.object.select_all(action="DESELECT") - - try: - for o in selected: - o.select = True - except: - pass - - active.select = True - context.scene.objects.active = active + self.restore_context(context) def find_hole(self, o): for child in o.children: @@ -1463,11 +1435,14 @@ class ARCHIPACK_PT_window(Panel): display_panels = BoolProperty( default=True ) + + @classmethod + def poll(cls, context): + return archipack_window.filter(context.active_object) def draw(self, context): o = context.active_object prop = archipack_window.datablock(o) - # prop = ARCHIPACK_PT_window.params(o) if prop is None: return layout = self.layout @@ -1585,87 +1560,23 @@ def draw(self, context): box.prop(prop, 'hole_inside_mat') box.prop(prop, 'hole_outside_mat') - @classmethod - def params(cls, o): - - try: - if 'archipack_window' not in o.data: - return False - else: - return o.data.archipack_window[0] - except: - return False - - @classmethod - def filter(cls, o): - try: - if 'archipack_window' not in o.data: - return False - else: - return True - except: - return False - - @classmethod - def poll(cls, context): - o = context.active_object - if o is None: - return False - return archipack_window.filter(o) - - + class ARCHIPACK_PT_window_panel(Panel): bl_idname = "ARCHIPACK_PT_window_panel" bl_label = "Window panel" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' - # bl_context = 'object' bl_category = 'ArchiPack' + + @classmethod + def poll(cls, context): + return archipack_window_panel.filter(context.active_object) def draw(self, context): - """ - o = context.active_object - o, prop = ARCHIPACK_PT_window_panel.params(o) - if prop is None: - return - """ layout = self.layout layout.operator("archipack.select_parent") - """ - layout.prop(prop, 'size', index=0) - layout.prop(prop, 'size', index=1) - layout.prop(prop, 'frame_x') - layout.prop(prop, 'frame_y') - layout.prop(prop, 'pivot') - layout.prop(prop, 'origin', index=0) - """ - - @classmethod - def params(cls, o): - if cls.filter(o): - if 'archipack_window_panel' in o.data: - return o, o.data.archipack_window_panel[0] - else: - for child in o.children: - o, props = cls.params(child) - if props is not None: - return o, props - return o, None - - @classmethod - def filter(cls, o): - try: - return bool('archipack_window_panel' in o.data) - except: - return False - - @classmethod - def poll(cls, context): - o = context.active_object - if o is None: - return False - return cls.filter(o) - + + # ------------------------------------------------------------------ # Define operator class to create object # ------------------------------------------------------------------ @@ -1783,7 +1694,10 @@ def unique(self, context): for child in context.selected_objects: if 'archipack_hole' in child: child.hide_select = True - + bpy.ops.object.select_all(action="DESELECT") + for o in sel: + o.select = True + # ----------------------------------------------------- # Execute # ----------------------------------------------------- @@ -2127,14 +2041,14 @@ def invoke(self, context, event): class ARCHIPACK_OT_window_preset_menu(PresetMenuOperator, Operator): bl_idname = "archipack.window_preset_menu" - bl_label = "Window Styles" + bl_label = "Window Presets" preset_subdir = "archipack_window" class ARCHIPACK_OT_window_preset(ArchipackPreset, Operator): """Add a Window Preset""" bl_idname = "archipack.window_preset" - bl_label = "Add Window Style" + bl_label = "Add Window Preset" preset_menu = "ARCHIPACK_OT_window_preset_menu" @property diff --git a/bmesh_utils.py b/bmesh_utils.py index c318691..fe5e3ef 100644 --- a/bmesh_utils.py +++ b/bmesh_utils.py @@ -47,6 +47,7 @@ def _end(bm, o): """ private, end bmesh editing of active object """ + bm.normal_update() bmesh.update_edit_mesh(o.data, True) bpy.ops.object.mode_set(mode='OBJECT') bm.free() @@ -91,13 +92,16 @@ def buildmesh(context, o, verts, faces, matids=None, uvs=None, weld=False, clean if weld: bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001) BmeshEdit._end(bm, o) + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') if auto_smooth: + bpy.ops.mesh.faces_shade_smooth() o.data.use_auto_smooth = True + else: + bpy.ops.mesh.faces_shade_flat() if clean: - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.delete_loose() - bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode='OBJECT') @staticmethod def verts(context, o, verts): From 934fa9aad528ffd987641aee4fe9ae07876bcb4b Mon Sep 17 00:00:00 2001 From: s-leger Date: Sun, 18 Jun 2017 18:52:18 +0200 Subject: [PATCH 05/17] [FEATURE] enhance draw door / window close issue #36 Changed default behaviour to linked objects by default. Display holes while drawing so window/doors no more hidden by wall. Adjust window/door depth according wall. ALT on invoke to use active object as basis for linked dups SHIFT on click to create an unlinked copy --- archipack_autoboolean.py | 57 +++++++++++++++++----- archipack_door.py | 102 ++++++++++++++++++++++++++++----------- archipack_preset.py | 13 +++-- archipack_wall2.py | 1 + archipack_window.py | 96 ++++++++++++++++++++++++------------ 5 files changed, 195 insertions(+), 74 deletions(-) diff --git a/archipack_autoboolean.py b/archipack_autoboolean.py index b1f5d5c..761fa3d 100644 --- a/archipack_autoboolean.py +++ b/archipack_autoboolean.py @@ -36,7 +36,7 @@ def debug_using_gl(context, filename): context.scene.update() temp_path = "C:\\tmp\\" - context.scene.render.filepath = path.join(temp_path, filename + ".png") + context.scene.render.filepath = path.join(temp_path, filename + ".png") bpy.ops.render.opengl(write_still=True) @@ -142,14 +142,17 @@ def _generate_hole(self, context, o): d = self.datablock(o) hole = None if d is not None: - # Keep separate as contains rules may vary from window to doors - if ((self._contains(o.location) or - self._contains(o.matrix_world * Vector((0, 0, 0.5 * d.z))))): + if (self.itM is not None and ( + self._contains(o.location) or + self._contains(o.matrix_world * Vector((0, 0, 0.5 * d.z)))) + ): if self.mode != 'ROBUST': hole = d.interactive_hole(context, o) else: hole = d.robust_hole(context, o.matrix_world) - # print("_generate_hole Generate hole %s" % (hole.name)) + # print("_generate_hole Generate hole %s" % (hole.name)) + else: + hole = d.interactive_hole(context, o) return hole def partition(self, array, begin, end): @@ -278,7 +281,7 @@ def update_hybrid(self, context, wall, childs, holes): existing.append(h) else: to_delete.append([m, h]) - + # remove modifier and holes not found in new list self.remove_modif_and_object(context, hole_obj, to_delete) # debug_using_gl(context, "276") @@ -287,7 +290,7 @@ def update_hybrid(self, context, wall, childs, holes): if h not in existing: self.union(hole_obj, h) # debug_using_gl(context, "281") - + # Interactive def update_interactive(self, context, wall, childs, holes): @@ -407,7 +410,7 @@ def autoboolean(self, context, wall): for hole in holes: self.prepare_hole(hole) # debug_using_gl(context, "401") - + # update / remove / add boolean modifier if self.mode == 'INTERACTIVE': self.update_interactive(context, wall, childs, holes) @@ -415,7 +418,7 @@ def autoboolean(self, context, wall): self.update_robust(context, wall, childs) else: self.update_hybrid(context, wall, childs, holes) - + bpy.ops.object.select_all(action='DESELECT') # parenting childs to wall reference point if wall.parent is None: @@ -434,15 +437,14 @@ def autoboolean(self, context, wall): o.hide_select = False o.select = True # debug_using_gl(context, "428") - + bpy.ops.archipack.parent_to_reference() for o in childs: if 'archipack_robusthole' in o: o.hide_select = True # debug_using_gl(context, "435") - - + def detect_mode(self, context, wall): for m in wall.modifiers: if m.type == 'BOOLEAN' and m.object is not None: @@ -635,11 +637,42 @@ def execute(self, context): return {'CANCELLED'} +class ARCHIPACK_OT_generate_hole(Operator): + bl_idname = "archipack.generate_hole" + bl_label = "Generate hole" + bl_description = "Generate interactive hole for doors and windows" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + if context.mode == "OBJECT": + manager = ArchipackBoolManager(mode='HYBRID') + o = context.active_object + d = manager.datablock(o) + if d is None: + self.report({'WARNING'}, "Archipack: active object must be a door or a window") + return {'CANCELLED'} + bpy.ops.object.select_all(action='DESELECT') + o.select = True + context.scene.objects.active = o + hole = manager._generate_hole(context, o) + manager.prepare_hole(hole) + hole.select = False + o.select = True + context.scene.objects.active = o + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + def register(): + bpy.utils.register_class(ARCHIPACK_OT_generate_hole) bpy.utils.register_class(ARCHIPACK_OT_single_boolean) bpy.utils.register_class(ARCHIPACK_OT_auto_boolean) def unregister(): + bpy.utils.unregister_class(ARCHIPACK_OT_generate_hole) bpy.utils.unregister_class(ARCHIPACK_OT_single_boolean) bpy.utils.unregister_class(ARCHIPACK_OT_auto_boolean) diff --git a/archipack_door.py b/archipack_door.py index 843ab8d..e349b79 100644 --- a/archipack_door.py +++ b/archipack_door.py @@ -565,7 +565,7 @@ def remove_handle(self, context, o): def update(self, context): o = self.find_in_selection(context) - + if o is None: return @@ -577,7 +577,7 @@ def update(self, context): self.update_handle(context, o) self.restore_context(context) - + class ARCHIPACK_PT_door_panel(Panel): bl_idname = "ARCHIPACK_PT_door_panel" @@ -590,12 +590,12 @@ class ARCHIPACK_PT_door_panel(Panel): @classmethod def poll(cls, context): return archipack_door_panel.filter(context.active_object) - + def draw(self, context): layout = self.layout layout.operator("archipack.select_parent") - - + + # ------------------------------------------------------------------ # Define operator class to create object # ------------------------------------------------------------------ @@ -1081,7 +1081,7 @@ def find_handle(self, o): def get_childs_panels(self, context, o): return [child for child in o.children if archipack_door_panel.filter(child)] - + def _synch_childs(self, context, o, linked, childs): """ sub synch childs nodes of linked object @@ -1286,7 +1286,7 @@ def find_hole(self, o): for child in o.children: if 'archipack_hole' in child: return child - return None + return None def interactive_hole(self, context, o): hole_obj = self.find_hole(o) @@ -1336,7 +1336,7 @@ class ARCHIPACK_PT_door(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'ArchiPack' - + @classmethod def poll(cls, context): return archipack_door.filter(context.active_object) @@ -1397,7 +1397,7 @@ def draw(self, context): box.prop(props, 'panel_border') box.prop(props, 'chanfer') - + # ------------------------------------------------------------------ # Define operator class to create object # ------------------------------------------------------------------ @@ -1567,8 +1567,12 @@ def update(self, context): for linked in context.selected_objects: if linked != o: archipack_door.datablock(linked).update(context) + bpy.ops.object.select_all(action="DESELECT") + o.select = True + context.scene.objects.active = o def unique(self, context): + act = context.active_object sel = [o for o in context.selected_objects] bpy.ops.object.select_all(action="DESELECT") for o in sel: @@ -1585,7 +1589,11 @@ def unique(self, context): for child in context.selected_objects: if 'archipack_hole' in child: child.hide_select = True - + bpy.ops.object.select_all(action="DESELECT") + context.scene.objects.active = act + for o in sel: + o.select = True + def execute(self, context): if context.mode == "OBJECT": if self.mode == 'CREATE': @@ -1617,7 +1625,7 @@ class ARCHIPACK_OT_door_draw(Operator): filepath = StringProperty(default="") feedback = None stack = [] - + def mouse_to_matrix(self, context, event): """ convert mouse pos to 3d point over plane defined by origin and normal @@ -1657,27 +1665,61 @@ def draw_callback(self, _self, context): self.feedback.draw(context) def add_object(self, context, event): + o = context.active_object + bpy.ops.object.select_all(action="DESELECT") + + if archipack_door.filter(o): + + o.select = True + context.scene.objects.active = o - if event.shift and archipack_door.filter(context.active_object): - o = context.active_object + if event.shift: + bpy.ops.archipack.door(mode="UNIQUE") + new_w = o.copy() new_w.data = o.data context.scene.objects.link(new_w) + + o = new_w + o.select = True + context.scene.objects.active = o + # synch subs from parent instance bpy.ops.archipack.door(mode="REFRESH") - bpy.ops.object.select_all(action="DESELECT") - new_w.select = True - context.scene.objects.active = new_w + else: bpy.ops.archipack.door(auto_manipulate=False, filepath=self.filepath) + o = context.active_object + bpy.ops.archipack.generate_hole('INVOKE_DEFAULT') + o.select = True + context.scene.objects.active = o + def modal(self, context, event): context.area.tag_redraw() + o = context.active_object + d = archipack_door.datablock(o) + hole = None + + if d is not None: + hole = d.find_hole(o) + + # hide hole from raycast + if hole is not None: + o.hide = True + hole.hide = True + res, tM, wall = self.mouse_to_matrix(context, event) - w = context.active_object - if res and archipack_door.filter(w): - w.matrix_world = tM + + if hole is not None: + o.hide = False + hole.hide = False + + if res and d is not None: + o.matrix_world = tM + if d.y != wall.data.archipack_wall2[0].width: + d.y = wall.data.archipack_wall2[0].width if event.value == 'PRESS': if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}: @@ -1687,10 +1729,10 @@ def modal(self, context, event): if bpy.ops.archipack.single_boolean.poll(): bpy.ops.archipack.single_boolean() wall.select = False - # w must be a door here - if archipack_door.filter(w): - context.scene.objects.active = w - self.stack.append(w) + # o must be a door here + if d is not None: + context.scene.objects.active = o + self.stack.append(o) self.add_object(context, event) context.active_object.matrix_world = tM return {'RUNNING_MODAL'} @@ -1698,20 +1740,19 @@ def modal(self, context, event): # prevent selection of other object if event.type in {'RIGHTMOUSE'}: return {'RUNNING_MODAL'} - + if self.keymap.check(event, self.keymap.undo) or ( event.type in {'BACK_SPACE'} and event.value == 'RELEASE' ): if len(self.stack) > 0: - o = context.active_object last = self.stack.pop() context.scene.objects.active = last bpy.ops.archipack.door(mode="DELETE") context.scene.objects.active = o return {'RUNNING_MODAL'} - + if event.value == 'RELEASE': - + if event.type in {'ESC', 'RIGHTMOUSE'}: bpy.ops.archipack.door(mode='DELETE') self.feedback.disable() @@ -1726,9 +1767,12 @@ def invoke(self, context, event): o = None self.stack = [] self.keymap = Keymaps(context) + # exit manipulate_mode if any bpy.ops.archipack.disable_manipulate() - if event.shift and archipack_door.filter(context.active_object): + # invoke with alt pressed will use current object as basis for linked copy + if self.filepath == '' and archipack_door.filter(context.active_object): o = context.active_object + context.scene.objects.active = None bpy.ops.object.select_all(action="DESELECT") if o is not None: o.select = True @@ -1738,7 +1782,7 @@ def invoke(self, context, event): self.feedback.instructions(context, "Draw a door", "Click & Drag over a wall", [ ('LEFTCLICK, RET, SPACE, ENTER', 'Create a door'), ('BACKSPACE, CTRL+Z', 'undo last'), - ('SHIFT', 'Make linked door'), + ('SHIFT', 'Make independant copy'), ('RIGHTCLICK or ESC', 'exit') ]) self.feedback.enable() diff --git a/archipack_preset.py b/archipack_preset.py index a6c1738..b403295 100644 --- a/archipack_preset.py +++ b/archipack_preset.py @@ -433,13 +433,20 @@ def modal(self, context, event): def invoke(self, context, event): if context.area.type == 'VIEW_3D': - # with shift pressed on invoke, will bypass menu operator and + # with alt pressed on invoke, will bypass menu operator and # call preset_operator - if event.shift: + # allow start drawing linked copy of active object + if event.alt: po = self.preset_operator.split(".") op = getattr(getattr(bpy.ops, po[0]), po[1]) - if op.poll(): + d = context.active_object.data + + if d is not None and self.preset_subdir in d and op.poll(): op('INVOKE_DEFAULT') + else: + self.report({'WARNING'}, "Active object must be a " + self.preset_subdir.split("_")[1].capitalize()) + return {'CANCELLED'} + return {'FINISHED'} self.menu = PresetMenu(context, self.preset_subdir) diff --git a/archipack_wall2.py b/archipack_wall2.py index 61f8187..8cfeb22 100644 --- a/archipack_wall2.py +++ b/archipack_wall2.py @@ -1796,6 +1796,7 @@ def invoke(self, context, event): ('CTRL', 'Snap'), ('MMBTN', 'Constraint to axis'), ('X Y', 'Constraint to axis'), + ('SHIFT+CTRL+TAB', 'Switch snap mode'), ('RIGHTCLICK or ESC', 'exit without change') ]) self.feedback.enable() diff --git a/archipack_window.py b/archipack_window.py index 22a2fc5..63d7433 100644 --- a/archipack_window.py +++ b/archipack_window.py @@ -386,9 +386,9 @@ def remove_handle(self, context, o): bpy.data.objects.remove(handle, do_unlink=True) def update(self, context): - + o = self.find_in_selection(context) - + if o is None: return @@ -1019,7 +1019,7 @@ def update_rows(self, context, o): def get_childs_panels(self, context, o): return [child for child in o.children if archipack_window_panel.filter(child)] - + def adjust_size_and_origin(self, size, origin, pivot, materials): if len(size) > 1: size[0].x += 0.5 * self.frame_x @@ -1435,7 +1435,7 @@ class ARCHIPACK_PT_window(Panel): display_panels = BoolProperty( default=True ) - + @classmethod def poll(cls, context): return archipack_window.filter(context.active_object) @@ -1560,14 +1560,14 @@ def draw(self, context): box.prop(prop, 'hole_inside_mat') box.prop(prop, 'hole_outside_mat') - + class ARCHIPACK_PT_window_panel(Panel): bl_idname = "ARCHIPACK_PT_window_panel" bl_label = "Window panel" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'ArchiPack' - + @classmethod def poll(cls, context): return archipack_window_panel.filter(context.active_object) @@ -1575,8 +1575,8 @@ def poll(cls, context): def draw(self, context): layout = self.layout layout.operator("archipack.select_parent") - - + + # ------------------------------------------------------------------ # Define operator class to create object # ------------------------------------------------------------------ @@ -1673,10 +1673,12 @@ def update(self, context): for linked in context.selected_objects: if linked != o: archipack_window.datablock(linked).update(context) + bpy.ops.object.select_all(action="DESELECT") o.select = True context.scene.objects.active = o def unique(self, context): + act = context.active_object sel = [o for o in context.selected_objects] bpy.ops.object.select_all(action="DESELECT") for o in sel: @@ -1695,9 +1697,10 @@ def unique(self, context): if 'archipack_hole' in child: child.hide_select = True bpy.ops.object.select_all(action="DESELECT") + context.scene.objects.active = act for o in sel: o.select = True - + # ----------------------------------------------------- # Execute # ----------------------------------------------------- @@ -1731,8 +1734,8 @@ class ARCHIPACK_OT_window_draw(Operator): filepath = StringProperty(default="") feedback = None - stack = [] - + stack = [] + def mouse_to_matrix(self, context, event): """ convert mouse pos to 3d point over plane defined by origin and normal @@ -1772,27 +1775,60 @@ def draw_callback(self, _self, context): self.feedback.draw(context) def add_object(self, context, event): + o = context.active_object + bpy.ops.object.select_all(action="DESELECT") + + if archipack_window.filter(o): + + o.select = True + context.scene.objects.active = o + + if event.shift: + bpy.ops.archipack.window(mode="UNIQUE") - if event.shift and archipack_window.filter(context.active_object): - o = context.active_object new_w = o.copy() new_w.data = o.data context.scene.objects.link(new_w) + + o = new_w + o.select = True + context.scene.objects.active = o + # synch subs from parent instance bpy.ops.archipack.window(mode="REFRESH") - bpy.ops.object.select_all(action="DESELECT") - new_w.select = True - context.scene.objects.active = new_w + else: bpy.ops.archipack.window(auto_manipulate=False, filepath=self.filepath) - + o = context.active_object + + bpy.ops.archipack.generate_hole('INVOKE_DEFAULT') + o.select = True + context.scene.objects.active = o + def modal(self, context, event): context.area.tag_redraw() + o = context.active_object + d = archipack_window.datablock(o) + hole = None + if d is not None: + hole = d.find_hole(o) + + # hide hole from raycast + if hole is not None: + o.hide = True + hole.hide = True + res, tM, wall = self.mouse_to_matrix(context, event) - w = context.active_object - if res and archipack_window.filter(w): - w.matrix_world = tM + + if hole is not None: + o.hide = False + hole.hide = False + + if res and d is not None: + o.matrix_world = tM + if d.y != wall.data.archipack_wall2[0].width: + d.y = wall.data.archipack_wall2[0].width if event.value == 'PRESS': if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}: @@ -1802,30 +1838,29 @@ def modal(self, context, event): if bpy.ops.archipack.single_boolean.poll(): bpy.ops.archipack.single_boolean() wall.select = False - # w must be a window here - if archipack_window.filter(w): - context.scene.objects.active = w - self.stack.append(w) + # o must be a window here + if d is not None: + context.scene.objects.active = o + self.stack.append(o) self.add_object(context, event) context.active_object.matrix_world = tM return {'RUNNING_MODAL'} # prevent selection of other object if event.type in {'RIGHTMOUSE'}: return {'RUNNING_MODAL'} - + if self.keymap.check(event, self.keymap.undo) or ( event.type in {'BACK_SPACE'} and event.value == 'RELEASE' ): if len(self.stack) > 0: - o = context.active_object last = self.stack.pop() context.scene.objects.active = last bpy.ops.archipack.window(mode="DELETE") context.scene.objects.active = o return {'RUNNING_MODAL'} - + if event.value == 'RELEASE': - + if event.type in {'ESC', 'RIGHTMOUSE'}: bpy.ops.archipack.window(mode='DELETE') self.feedback.disable() @@ -1843,8 +1878,9 @@ def invoke(self, context, event): # exit manipulate_mode if any bpy.ops.archipack.disable_manipulate() # invoke with shift pressed will use current object as basis for linked copy - if event.shift and archipack_window.filter(context.active_object): + if self.filepath == '' and archipack_window.filter(context.active_object): o = context.active_object + context.scene.objects.active = None bpy.ops.object.select_all(action="DESELECT") if o is not None: o.select = True @@ -1854,7 +1890,7 @@ def invoke(self, context, event): self.feedback.instructions(context, "Draw a window", "Click & Drag over a wall", [ ('LEFTCLICK, RET, SPACE, ENTER', 'Create a window'), ('BACKSPACE, CTRL+Z', 'undo last'), - ('SHIFT', 'Make linked window'), + ('SHIFT', 'Make independant copy'), ('RIGHTCLICK or ESC', 'exit') ]) self.feedback.enable() From d624cca33b7540507496027f1b46155970f49ec6 Mon Sep 17 00:00:00 2001 From: s-leger Date: Mon, 19 Jun 2017 00:07:29 +0200 Subject: [PATCH 06/17] [FEATURE] Support emulate 3 btn in draw tools Allow ctrl and alt to draw linked windows/doors from active object --- archipack_gl.py | 4 +++- archipack_preset.py | 19 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/archipack_gl.py b/archipack_gl.py index a39ce6d..8201b8d 100644 --- a/archipack_gl.py +++ b/archipack_gl.py @@ -723,6 +723,8 @@ def pts(self): return self.pts_2d def draw(self, context, render=False): + if self.image is None: + return bgl.glPushAttrib(bgl.GL_ENABLE_BIT) p0 = self.pts[0] p1 = self.pts[1] @@ -893,7 +895,7 @@ def sensor_center(self): class ThumbHandle(GlHandle): - def __init__(self, size_2d, image, label, draggable=False, selectable=False, d=2): + def __init__(self, size_2d, label, image=None, draggable=False, selectable=False, d=2): GlHandle.__init__(self, size_2d, size_2d, draggable, selectable, d) self.image = GlImage(image=image) self.label = GlText(d=2, label=label.replace("_", " ").capitalize()) diff --git a/archipack_preset.py b/archipack_preset.py index b403295..f89997e 100644 --- a/archipack_preset.py +++ b/archipack_preset.py @@ -168,10 +168,10 @@ def sensor_center(self): class PresetMenuItem(): - def __init__(self, thumbsize, image, preset): + def __init__(self, thumbsize, preset, image=None): name = bpy.path.display_name_from_filepath(preset) self.preset = preset - self.handle = ThumbHandle(thumbsize, image, name, draggable=True) + self.handle = ThumbHandle(thumbsize, name, image, draggable=True) self.enable = True def filter(self, keywords): @@ -179,7 +179,7 @@ def filter(self, keywords): if key not in self.handle.label.label: return False return True - + def set_pos(self, context, pos): self.handle.set_pos(context, pos) @@ -274,6 +274,10 @@ def clearImages(self): self.imageList.clear() def make_menuitem(self, filepath): + """ + @TODO: + Lazy load images + """ image = None img_idx = bpy.data.images.find(os.path.basename(filepath) + '.png') if img_idx > -1: @@ -284,7 +288,7 @@ def make_menuitem(self, filepath): self.imageList.append(image) if image is None: image = self.default_image - item = PresetMenuItem(self.thumbsize, image, filepath + '.py') + item = PresetMenuItem(self.thumbsize, filepath + '.py', image) self.menuItems.append(item) def set_pos(self, context): @@ -314,7 +318,7 @@ def set_pos(self, context): if len(keywords) > 0 and not item.filter(keywords): item.enable = False continue - + item.set_pos(context, Vector((x, y))) x += self.thumbsize.x + self.spacing.x if x > x_max: @@ -389,6 +393,8 @@ def draw_handler(self, _self, context): self.menu.draw(context) def modal(self, context, event): + if self.menu is None: + return {'FINISHED'} context.area.tag_redraw() if event.type == 'MOUSEMOVE': self.menu.mouse_move(context, event) @@ -436,7 +442,7 @@ def invoke(self, context, event): # with alt pressed on invoke, will bypass menu operator and # call preset_operator # allow start drawing linked copy of active object - if event.alt: + if event.alt or event.ctrl: po = self.preset_operator.split(".") op = getattr(getattr(bpy.ops, po[0]), po[1]) d = context.active_object.data @@ -446,7 +452,6 @@ def invoke(self, context, event): else: self.report({'WARNING'}, "Active object must be a " + self.preset_subdir.split("_")[1].capitalize()) return {'CANCELLED'} - return {'FINISHED'} self.menu = PresetMenu(context, self.preset_subdir) From 9b4201c881fead1646d785d52e9daf3ab816375d Mon Sep 17 00:00:00 2001 From: s-leger Date: Mon, 19 Jun 2017 01:44:17 +0200 Subject: [PATCH 07/17] [FEATURE] Fence from curve with many splines --- archipack_fence.py | 84 ++++++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 33 deletions(-) diff --git a/archipack_fence.py b/archipack_fence.py index b739104..af93fba 100644 --- a/archipack_fence.py +++ b/archipack_fence.py @@ -771,7 +771,12 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): 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, @@ -1228,13 +1233,25 @@ def interpolate_bezier(self, pts, wM, p0, p1, resolution): for i in range(resolution): pts.append(seg[i].to_3d()) - def from_spline(self, wM, resolution, spline): + 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] @@ -1247,7 +1264,7 @@ def from_spline(self, wM, resolution, spline): pts.append(pts[0]) else: pts.append(wM * points[-1].co) - + self.auto_update = False self.n_parts = len(pts) - 1 @@ -1270,11 +1287,20 @@ def from_spline(self, wM, resolution, spline): p0 = p1 self.auto_update = True - + + o.matrix_world = tM * Matrix([ + [1, 0, 0, pt.x], + [0, 1, 0, pt.y], + [0, 0, 1, pt.z], + [0, 0, 0, 1] + ]) + def update_path(self, context): - 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(user_def_path.matrix_world, self.user_defined_resolution, user_def_path.data.splines[0]) + path = context.scene.objects.get(self.user_defined_path) + if path is not None and path.type == 'CURVE': + splines = path.data.splines + if len(splines) > self.user_defined_spline: + self.from_spline(context, path.matrix_world, self.user_defined_resolution, splines[self.user_defined_spline]) def get_generator(self): g = FenceGenerator(self.parts) @@ -1445,10 +1471,11 @@ def draw(self, context): 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') - - box.prop(prop, 'user_defined_resolution') - box.prop(prop, 'x_offset') + if prop.user_defined_path is not "": + box.prop(prop, 'user_defined_spline') + box.prop(prop, 'user_defined_resolution') box.prop(prop, 'angle_limit') + box.prop(prop, 'x_offset') box = layout.box() row = box.row() if prop.parts_expand: @@ -1647,35 +1674,26 @@ def draw(self, context): row.label("Use Properties panel (N) to define parms", icon='INFO') def create(self, context): + o = None curve = context.active_object - bpy.ops.archipack.fence('INVOKE_DEFAULT', auto_manipulate=False) - o = context.active_object - d = archipack_fence.datablock(o) - d.user_defined_path = curve.name - d.update_path(context) - spline = curve.data.splines[0] - if spline.type == 'POLY': - pt = spline.points[0].co - elif spline.type == 'BEZIER': - pt = spline.bezier_points[0].co - else: - pt = Vector((0, 0, 0)) - # pretranslate - o.matrix_world = curve.matrix_world * Matrix([ - [1, 0, 0, pt.x], - [0, 1, 0, pt.y], - [0, 0, 1, pt.z], - [0, 0, 0, 1] - ]) + 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) - o.select = True - context.scene.objects.active = o - self.manipulate() + if o is not None: + o.select = True + context.scene.objects.active = o + # self.manipulate() return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") @@ -1721,7 +1739,7 @@ class ARCHIPACK_OT_fence_preset(ArchipackPreset, Operator): @property def blacklist(self): - return ['manipulators', 'n_parts', 'parts', 'user_defined_path'] + return ['manipulators', 'n_parts', 'parts', 'user_defined_path', 'user_defined_spline'] def register(): From 31b013222cf388f9daf86261783654c4a7f33f45 Mon Sep 17 00:00:00 2001 From: s-leger Date: Mon, 19 Jun 2017 21:04:15 +0200 Subject: [PATCH 08/17] Update archipack_toolkit.py --- archipack_toolkit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archipack_toolkit.py b/archipack_toolkit.py index 77f6299..214ffc0 100644 --- a/archipack_toolkit.py +++ b/archipack_toolkit.py @@ -176,7 +176,7 @@ def update(self, context): self.manipulators[2].set_pts([(x, -y, 0), (x, -y, self.z), (-1, 0, 0)]) # always restore context - self.restore_context() + self.restore_context(context) class ARCHIPACK_PT_myobject(Panel): From 8d32c449c703c024268c63d5d7265f4280044aa5 Mon Sep 17 00:00:00 2001 From: s-leger Date: Tue, 20 Jun 2017 11:55:39 +0200 Subject: [PATCH 09/17] [FEATURE] Take account of z axis in angle_limit --- archipack_fence.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/archipack_fence.py b/archipack_fence.py index af93fba..9549f73 100644 --- a/archipack_fence.py +++ b/archipack_fence.py @@ -172,7 +172,9 @@ def param_t(self, angle_limit, post_spacing): for i, f in enumerate(self.segs): f.dist = self.length self.length += f.line.length - + + vz0 = Vector((1, 0)) + for i, f in enumerate(self.segs): if f.dist > 0: f.t_start = f.dist / self.length @@ -184,7 +186,10 @@ def param_t(self, angle_limit, post_spacing): f.z0 = z f.dz = dz z += dz - if i < n_parts and abs(self.parts[i + 1].a0) >= angle_limit: + vz1 = Vector((f.length, f.dz)) + angle_z = abs(vz0.angle_signed(vz1)) + vz0 = vz1 + if i < n_parts and (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)) From e6bca91585a5171977ed0134f1aa96fe5d922949 Mon Sep 17 00:00:00 2001 From: s-leger Date: Tue, 20 Jun 2017 19:53:16 +0200 Subject: [PATCH 10/17] [BUGFIX] fence angle z --- archipack_fence.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/archipack_fence.py b/archipack_fence.py index 9549f73..a2f9cfd 100644 --- a/archipack_fence.py +++ b/archipack_fence.py @@ -174,32 +174,37 @@ def param_t(self, angle_limit, post_spacing): 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 - dz = self.parts[i].dz f.z0 = z f.dz = dz z += dz - vz1 = Vector((f.length, f.dz)) - angle_z = abs(vz0.angle_signed(vz1)) - vz0 = vz1 - if i < n_parts and (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) - + + 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() From 9cac3351699a46d779477276edb9501d6752fe80 Mon Sep 17 00:00:00 2001 From: s-leger Date: Wed, 21 Jun 2017 14:51:34 +0200 Subject: [PATCH 11/17] [FEATURE] Floor presets --- presets/archipack_floor/herringbone_50x10.png | Bin 0 -> 11148 bytes presets/archipack_floor/herringbone_50x10.py | 34 ++++++++++++++++++ .../archipack_floor/herringbone_p_50x10.png | Bin 0 -> 10924 bytes .../archipack_floor/herringbone_p_50x10.py | 34 ++++++++++++++++++ presets/archipack_floor/parquet_15x3.png | Bin 0 -> 13445 bytes presets/archipack_floor/parquet_15x3.py | 34 ++++++++++++++++++ presets/archipack_floor/planks_200x20.png | Bin 0 -> 11644 bytes presets/archipack_floor/planks_200x20.py | 34 ++++++++++++++++++ presets/archipack_floor/tiles_15x15.png | Bin 0 -> 12939 bytes presets/archipack_floor/tiles_15x15.py | 34 ++++++++++++++++++ presets/archipack_floor/tiles_60x30.png | Bin 0 -> 11379 bytes presets/archipack_floor/tiles_60x30.py | 34 ++++++++++++++++++ presets/archipack_floor/tiles_hex_10x10.png | Bin 0 -> 13663 bytes presets/archipack_floor/tiles_hex_10x10.py | 34 ++++++++++++++++++ .../tiles_l+ms_30x30_15x15.png | Bin 0 -> 12511 bytes .../archipack_floor/tiles_l+ms_30x30_15x15.py | 34 ++++++++++++++++++ .../archipack_floor/tiles_l+s_30x30_15x15.png | Bin 0 -> 11631 bytes .../archipack_floor/tiles_l+s_30x30_15x15.py | 34 ++++++++++++++++++ 18 files changed, 306 insertions(+) create mode 100644 presets/archipack_floor/herringbone_50x10.png create mode 100644 presets/archipack_floor/herringbone_50x10.py create mode 100644 presets/archipack_floor/herringbone_p_50x10.png create mode 100644 presets/archipack_floor/herringbone_p_50x10.py create mode 100644 presets/archipack_floor/parquet_15x3.png create mode 100644 presets/archipack_floor/parquet_15x3.py create mode 100644 presets/archipack_floor/planks_200x20.png create mode 100644 presets/archipack_floor/planks_200x20.py create mode 100644 presets/archipack_floor/tiles_15x15.png create mode 100644 presets/archipack_floor/tiles_15x15.py create mode 100644 presets/archipack_floor/tiles_60x30.png create mode 100644 presets/archipack_floor/tiles_60x30.py create mode 100644 presets/archipack_floor/tiles_hex_10x10.png create mode 100644 presets/archipack_floor/tiles_hex_10x10.py create mode 100644 presets/archipack_floor/tiles_l+ms_30x30_15x15.png create mode 100644 presets/archipack_floor/tiles_l+ms_30x30_15x15.py create mode 100644 presets/archipack_floor/tiles_l+s_30x30_15x15.png create mode 100644 presets/archipack_floor/tiles_l+s_30x30_15x15.py diff --git a/presets/archipack_floor/herringbone_50x10.png b/presets/archipack_floor/herringbone_50x10.png new file mode 100644 index 0000000000000000000000000000000000000000..b6e7fe56713f2bdcc5ee2a65de012ce461fb2164 GIT binary patch literal 11148 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 ah#BI1)7CB3d2chSaLV!;auqTbLH`37Ka36l literal 0 HcmV?d00001 diff --git a/presets/archipack_floor/herringbone_50x10.py b/presets/archipack_floor/herringbone_50x10.py new file mode 100644 index 0000000..0317331 --- /dev/null +++ b/presets/archipack_floor/herringbone_50x10.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.space_l = 0.004999999888241291 +d.is_width_vary = False +d.offset_vary = 47.810237884521484 +d.is_ran_thickness = False +d.b_length = 2.0 +d.t_length = 0.30000001192092896 +d.space_w = 0.004999999888241291 +d.t_width_s = 0.10000000149011612 +d.b_length_s = 0.5 +d.is_grout = False +d.tile_types = '24' +d.offset = 50.0 +d.width_vary = 50.0 +d.spacing = 0.0010000000474974513 +d.is_offset = True +d.is_bevel = False +d.is_random_offset = True +d.bevel_amo = 0.001500000013038516 +d.thickness = 0.019999999552965164 +d.bevel_res = 1 +d.max_boards = 2 +d.b_width = 0.10000000149011612 +d.length_vary = 50.0 +d.ran_thickness = 50.0 +d.is_mat_vary = True +d.hb_direction = '1' +d.mat_vary = 3 +d.num_boards = 5 +d.t_width = 0.30000001192092896 +d.grout_depth = 0.0010000003967434168 +d.is_length_vary = False diff --git a/presets/archipack_floor/herringbone_p_50x10.png b/presets/archipack_floor/herringbone_p_50x10.png new file mode 100644 index 0000000000000000000000000000000000000000..1a2b2370eaaa413f5f2984c44fd1a52815615116 GIT binary patch literal 10924 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 GLH`H4!h9G2 literal 0 HcmV?d00001 diff --git a/presets/archipack_floor/herringbone_p_50x10.py b/presets/archipack_floor/herringbone_p_50x10.py new file mode 100644 index 0000000..a5ed42f --- /dev/null +++ b/presets/archipack_floor/herringbone_p_50x10.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.space_l = 0.004999999888241291 +d.is_width_vary = False +d.offset_vary = 47.810237884521484 +d.is_ran_thickness = False +d.b_length = 2.0 +d.t_length = 0.30000001192092896 +d.space_w = 0.004999999888241291 +d.t_width_s = 0.10000000149011612 +d.b_length_s = 0.5 +d.is_grout = False +d.tile_types = '23' +d.offset = 50.0 +d.width_vary = 50.0 +d.spacing = 0.0010000000474974513 +d.is_offset = True +d.is_bevel = False +d.is_random_offset = True +d.bevel_amo = 0.001500000013038516 +d.thickness = 0.019999999552965164 +d.bevel_res = 1 +d.max_boards = 2 +d.b_width = 0.10000000149011612 +d.length_vary = 50.0 +d.ran_thickness = 50.0 +d.is_mat_vary = True +d.hb_direction = '1' +d.mat_vary = 3 +d.num_boards = 5 +d.t_width = 0.30000001192092896 +d.grout_depth = 0.0010000003967434168 +d.is_length_vary = False diff --git a/presets/archipack_floor/parquet_15x3.png b/presets/archipack_floor/parquet_15x3.png new file mode 100644 index 0000000000000000000000000000000000000000..2b35d58b765186284c61c0d0f14e7cf28c58e79b GIT binary patch literal 13445 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 literal 0 HcmV?d00001 diff --git a/presets/archipack_floor/parquet_15x3.py b/presets/archipack_floor/parquet_15x3.py new file mode 100644 index 0000000..77fc9ee --- /dev/null +++ b/presets/archipack_floor/parquet_15x3.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.bevel_res = 1 +d.b_width = 0.029999999329447746 +d.is_bevel = False +d.hb_direction = '1' +d.is_width_vary = False +d.b_length = 2.0 +d.spacing = 0.0010000000474974513 +d.is_grout = False +d.num_boards = 5 +d.is_length_vary = False +d.thickness = 0.019999999552965164 +d.is_ran_thickness = False +d.is_random_offset = True +d.offset_vary = 47.810237884521484 +d.is_mat_vary = True +d.tile_types = '22' +d.length_vary = 50.0 +d.space_w = 0.004999999888241291 +d.ran_thickness = 50.0 +d.max_boards = 2 +d.t_width_s = 0.10000000149011612 +d.t_width = 0.30000001192092896 +d.t_length = 0.30000001192092896 +d.width_vary = 50.0 +d.mat_vary = 3 +d.grout_depth = 0.0010000003967434168 +d.is_offset = True +d.space_l = 0.004999999888241291 +d.bevel_amo = 0.001500000013038516 +d.offset = 50.0 +d.b_length_s = 2.0 diff --git a/presets/archipack_floor/planks_200x20.png b/presets/archipack_floor/planks_200x20.png new file mode 100644 index 0000000000000000000000000000000000000000..94a49c5789a2fe9d499563334f017b027a22728b GIT binary patch literal 11644 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 literal 0 HcmV?d00001 diff --git a/presets/archipack_floor/planks_200x20.py b/presets/archipack_floor/planks_200x20.py new file mode 100644 index 0000000..5fc46ec --- /dev/null +++ b/presets/archipack_floor/planks_200x20.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.bevel_res = 1 +d.b_width = 0.2 +d.is_bevel = True +d.hb_direction = '1' +d.is_width_vary = False +d.b_length = 2.0 +d.spacing = 0.002 +d.is_grout = False +d.num_boards = 4 +d.is_length_vary = False +d.thickness = 0.02 +d.is_ran_thickness = False +d.is_random_offset = True +d.offset_vary = 47.81 +d.is_mat_vary = True +d.tile_types = '21' +d.length_vary = 50.0 +d.space_w = 0.002 +d.ran_thickness = 50.0 +d.max_boards = 2 +d.t_width_s = 0.1 +d.t_width = 0.3 +d.t_length = 0.3 +d.width_vary = 50.0 +d.mat_vary = 3 +d.grout_depth = 0.001 +d.is_offset = True +d.space_l = 0.002 +d.bevel_amo = 0.0015 +d.offset = 50.0 +d.b_length_s = 2.0 diff --git a/presets/archipack_floor/tiles_15x15.png b/presets/archipack_floor/tiles_15x15.png new file mode 100644 index 0000000000000000000000000000000000000000..2a3d8633e632f4ae65e8bea9b7682ca50ca8cf79 GIT binary patch literal 12939 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 literal 0 HcmV?d00001 diff --git a/presets/archipack_floor/tiles_60x30.py b/presets/archipack_floor/tiles_60x30.py new file mode 100644 index 0000000..e43f187 --- /dev/null +++ b/presets/archipack_floor/tiles_60x30.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.b_width = 0.20000000298023224 +d.width_vary = 50.0 +d.t_width_s = 0.20000000298023224 +d.is_grout = True +d.tile_types = '1' +d.space_l = 0.004999999888241291 +d.is_length_vary = False +d.hb_direction = '1' +d.offset_vary = 50.0 +d.offset = 50.0 +d.spacing = 0.004999999888241291 +d.thickness = 0.10000000149011612 +d.bevel_res = 1 +d.is_offset = False +d.grout_depth = 0.0010000003967434168 +d.t_width = 0.30000001192092896 +d.is_ran_thickness = False +d.is_mat_vary = False +d.is_random_offset = False +d.space_w = 0.004999999888241291 +d.is_bevel = True +d.ran_thickness = 50.0 +d.max_boards = 2 +d.t_length = 0.6000000238418579 +d.b_length_s = 2.0 +d.bevel_amo = 0.001500000013038516 +d.is_width_vary = False +d.num_boards = 4 +d.length_vary = 50.0 +d.b_length = 0.800000011920929 +d.mat_vary = 1 diff --git a/presets/archipack_floor/tiles_hex_10x10.png b/presets/archipack_floor/tiles_hex_10x10.png new file mode 100644 index 0000000000000000000000000000000000000000..4d4c8ecf612c11735572e97f4ba3daee0fe1ed0c GIT binary patch literal 13663 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 R2m1%8ywP}F`pV+d{{h;I3oZZv literal 0 HcmV?d00001 diff --git a/presets/archipack_floor/tiles_l+ms_30x30_15x15.py b/presets/archipack_floor/tiles_l+ms_30x30_15x15.py new file mode 100644 index 0000000..85d2f21 --- /dev/null +++ b/presets/archipack_floor/tiles_l+ms_30x30_15x15.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.bevel_res = 1 +d.b_width = 0.20000000298023224 +d.is_bevel = True +d.hb_direction = '1' +d.is_width_vary = False +d.b_length = 0.800000011920929 +d.spacing = 0.004999999888241291 +d.is_grout = True +d.num_boards = 4 +d.is_length_vary = False +d.thickness = 0.10000000149011612 +d.is_ran_thickness = False +d.is_random_offset = False +d.offset_vary = 50.0 +d.is_mat_vary = False +d.tile_types = '3' +d.length_vary = 50.0 +d.space_w = 0.004999999888241291 +d.ran_thickness = 50.0 +d.max_boards = 2 +d.t_width_s = 0.20000000298023224 +d.t_width = 0.30000001192092896 +d.t_length = 0.30000001192092896 +d.width_vary = 50.0 +d.mat_vary = 1 +d.grout_depth = 0.0010000003967434168 +d.is_offset = False +d.space_l = 0.004999999888241291 +d.bevel_amo = 0.001500000013038516 +d.offset = 50.0 +d.b_length_s = 2.0 diff --git a/presets/archipack_floor/tiles_l+s_30x30_15x15.png b/presets/archipack_floor/tiles_l+s_30x30_15x15.png new file mode 100644 index 0000000000000000000000000000000000000000..33d286573abb0ff831ffb30ba7d3dad7e2616bff GIT binary patch literal 11631 zcmYLvcT^MI^LGLY(nLi?x&??xFM`q)6{$gb4M>q1PSEYl zq4_tPb#Gk|y7#Y5JOBU&mVW~XNJ+bM0p9X_Y3%vR(Zvq%Le~7fyPb!fr-z8Oi=C@& z3!6peg}|ME0x#Zr+5sfQ#AQUqq(vpf?}|&wN<5Jj6GP`3^Z+&y?{sqYN4^+9p+}{HJA8|M*tz9q}{xS4y>|E^t|C^WZ zS^V>Z%R41Bsv{&3x>nX*Z|Ib2W5h?rE<0BcM%Ee8xPvrQwgzAJNcR_+zv zg=rG+c-&>M*BXz!O8ZNa8m<(!EXcE4obkck!m+NKd$4i2zGa{O8$qaX~cf!bcy`!-y^TrM!ymf zKA!3mO`cyWz$%-LT~eFJ@fwCoR}1KWK(4pjOxYLsVfFkD3Z1_;i(*NiF3fXkn84Pv z{CxH7)K)v;02}`1OxA2a&nF=o(Ulp0dXAhfvHqdCt|-fGlDbnEOoHt@Vjfu>4{k#+ zio&Tr3H8X%bUgy*_?t5aS7*ZcvxJ$Ts-9nq-s8C?@yT@QJ>YL; zi8Ya%$iLHu10TFx9-N0fs&6;$6nE7th@RMz9Spd1%J8;0lr%%yb zxqhP>_fBT|{U2h4V5x(YZN&#OxthF@%)CaMRm16Ko(fR#7)M1RivpBV6%O+3F0jpo z8Z;G0zquOVCk!oX*y$nGu4bev9F6Lso0U$sh@#B$VsJv{d0L-Hor8Bb7LC?$!qzXT z@T5dO%s?kvx_aYIRn8APiD$4fF6(@mb}cqJ(3I%pfX%i_SiNssv57s2f)28gNX;tryDMYS59Ua+AH|Ikan6%?YG!rJe zwK=dKT`^{6Q``v8ecbGMz`ix?yD9pe5o=Io=FE-P+xj^Q4eS=I2_Uq2Po^l~oODhp zGr8=Hj>PPyi@NR4*SRXqSEwD7+Mu@YY-H43=t@{MZrl>cp?G&_4YmD^lO}ghY=3^& z3s0U;ET+8)QA^tkeNx!MtL=m_1I|^X*eW|!-b$ktIutEk1}|Xo0w%1yUN0wfjaEw3 z=Y%-jjFKDBx28d4%*x{`&46DhP5ckx)LWSrCfnGT45u!H<2!8KBdgP8n2GM>1zZ^M zfyO;PacgzosX?fdDpKlYsx(Zrrp(faU($VJ8iF8?VbN7-+v?1xrzCVi@rk|#$q<*v zg5dxFTXC1KEY~o+4%#-f08Zqd1vxoP=QEo~4>i58hA<|wniW`is#HKh`yBI%kQQ0oH6i5-BwEKtu7wHZISjn$6a$HDC**G4k9;V zZv7Wb4+Yz-$?I)UJk@|buDcns!@p`5DxC!t<@#McEEoGv#G&zx(jD|3N>(>>Aux+n zZ^zu^3GRUXovY5P{=Ptg5Ahzko$FWT+^Q;pNKZ%LTE3||z67JY;}qP#1Wko}Ka9MJ z#1%Yf`O7|>nK#1_8_Mh6#R$E%f+2jn%5;=?Gl{o?vmv8|_Bq9wywRj|T9{DQu#$p3 zYA>hBn(-XbLSrxt1ZS7m_&c|GG-eRXFJKWqABnHW__0WF!<$jPsq2p1Fl#MekJ;6E zwbBjpgk|O_y5YaPjcr+(8=x6WD?c=!Klx=vU7nhvBYV5=IP2dGMj+O|ZxUt> zpqqJd`DH51jAi4n_`Hqf9v=U#)@rZ7?#&YZ!wSFWT06Hjw`6 zGPY2S-so?U65Zt+ESgc-^C#~Ho~{N~+nqnCvNe^4Qk53cVk)|i)z$`d2fzI+3dkjp zjz4S?Iwi2GL3!Woqj$8tE`=G^7Wjz1^mZ0h;9vU0&Pj{(c8x))`hC9&%De>{v)z5g zf4VZ2vRKvdn9buYCLH>^o_WV&TjJK2$6!wnVPt=4n=XJ=888xgd8u~N$6lU0_GxfI zW}1cW?1A$m@5N6G3FF=K!r0sxOn^5dckD#ZZDzXTNpF+OhkMv<{6VLZ^g4%IZ64P! z-W;>MQAKFT_Rh2y)b9#;UOkG#KH^t=E;m$J9YG9n^Izo98hL2j0CL3D4R-j}=6&O$ zUXm}={t;*yvh5j6AKP{tmGRf=tHd>PJxBzrBv$^7{DA*|4sx2lr9!0YOAF6^T1I6p zynI!gTj1t~8sF(?K2Hv#x{8XiNrI-D{n_JxZjd_6lKe5{{XobIxJn;inK z8X|0!%pjs?g7kM>9tcX4{SnEGYh!*6d2m9|{$|90|FUfj%-~>#-z0VE@jYin!)?)! zgQlGSI^h*EV=t9tC2oKe%phw#mvdFuDaQ|DD$`@~`dXe2h;QX!FM(ym=2Vc+R24s}bLjv|a+yn-`ObS%CViDzPF$-J zvXOJSqJX)#lo$B1WxF$zQx(a4pW-so%_#9LfQ4m!tGyHUK>=y8YhsqR?dcYxvPnMh zmmxK#(Qbx+4!>K46B_W|XuH^hMGB*aF8yhhX+G+E5dW$*b+en7Go3V(_MUoJZ#mqb zw4yP+>O{9QN-$3yyo}%n$!ZFbmegdD23Rx0UBtDQ<<(lGy&pH2xg#Uk*VO%Uwj4 zOMgDzKr~5o^eoxlpd62yh+L$?aeO5jJ82m)~TIllhmYqtO|Oi39D>3VRjCL2q$%se&Hl_q z7XJJ7vg1FW{zA8SolVysxuOkkSo*-kUU3aQP7qz4Gn=Itq7`rbckG^IrizI@lkXM} z$}&^cN_xil5K@%%cf;?u1RzeuyZ&1zqDMuMXZ+~Kr_RtOh=gQ$9Cj5*BQ_6=7t)%_ zb;bzpSOxu-lx{IZxDaS{lbcr3o#-^43p!dR+-LTk3PnwbzmdMCrlnnVpnIRAZ#hU8 zQ(!U2VON%$PWuN5dm~zH3)(W}%Xe8k&HVrwh3Vku`qarsaI6^vcUlJRYTNC+q7Bre&U z3zmz1mZ4@s``%ycbAWzC!2+A9)|hjF9A$Zk;jZHjJ$c1o*!$txCM~wX#;w1CXm9}02JKOG{a3Yj3WTm=@r%my`Y+{IH0*<0 zfO!^a!OpKs3#hc}i)@}+@P~R-rK(wrWZn9Zs?XA$jw8b=MsX(dgUQQF`i<$37;}zUpl(F-ViD7WUfo&ut!0B=fcKWT70?s8Nq( z1y)-+gQ$t^Lk{-IQ8BTt2fu7)|5YsXPTe~5&d8mE#p1@`%ALPDoz8M0lAodew-;l} zpN|y$VUztjK{51SknkhhKU!0AgFBLw(_4vDK->WtRhW?7{^I3GgZ8#& zUK0wBcU|P?R0~X{HM>V!W-A21DVz%50`J*!c2(~@Ih%v=h{|7)RJ{1^Rjgm!$JrYh zq&AUv;WS7i?JF_QX&x-SZ5TMs))i^;YrmQ^#e26)q*Eb#fr?Q2fYufHRs^|OD3LXU zoSu^jd2Y00Lrco{M`&?c#oilNwq$A_$}8e%O)%V}P`9*rewbnKT>Q`w%L;Sg8Z~{V zc@+SB9IF9rY#p#wG-**DxDP;o6=>G8lLlo6_EGD)Ydtzz7<)HjZLU%eEd@&ry5Sr6 zZI3{>$*S_AeQ8s#vVM7Bc$j*_qiA&7x^ssjrF+zeIg%$q{teY9~0T)VL1 z<*68a(on~wYNj1DIWi+}+$|PN)0cRe`nniRsbd9Rd1IIRlFZ7+hTYj5 z8-0)Y-80&5ke*p6`t$KprvB*f{vNp#Y5&M|<#FD4X3v@o1SOlw1ED``n{F7bb(c8b zRXWQ#bqMYM5?uDhU$s!A5OrEhOK&Wtvvlgxq8mdaNotRLdYG%JFt5f>*Qo7;HhcS> z&N;-W5)=Gr>Ve}uP{EX=O(Y$8+H*wck zT7E5_?ya1h1(dDWk7B|u>iLF@YcccQn~>;O931kK+K=1{zgY_nFZ1@^<)v6jNPb8@ z`Ok_&EX5OIWSyFq65x*P8dkm6!n928-lO8B%@Vc=#w*~fzZ&wS(h6EM)NY^gSe`5p zJ2>22AOd@F^AA5J5=YuCk9v%n-W?6Ig{lQDNwkz+nUA{kyLXj6&vWIcMp(V%+TduLMv1x;)wJ8*n9=*enl%5=EB`2x)fa2E#X?qqiy4>S_*3! z?u{Uy-@jjF`r$vDTgnXqtpF~%m1=BAZ^`ZJig$t6G@_c`u>h7@G%}h-g2OspIz{XX zCbe8b^Rdvh+syUwlbW&XQ8qjtuGPe(sM? zY{M;BZ6%lgs@$xjJ`JuS=~+xRe?Mid?-jkcBqNvT)_cT(Ju8+`_>)P2jWV372vT3r zJFXPMld{A}^}O?j_h(Pu{2~6wQ$1KJ-c1-6_=mfLHMr#)eu>SOx14_SJZ44Y!>&VTyCr2fu_-@(Sb9!& zgsI4{i&wLgN}i0;0BpSgWfk)US$TTZ}GzO76Y5qy>IrkiWQS&PpH^r|GV3j zzCU)PO%jex3){V33c|V|53R65fRunOP09-qT-P=ApKM8^f;nc1y}_|vBe$1@LvL#cJ-dd=?yW3!3_ zn`mOf;zw&Ml`L9+&f&F1TAWyPLb4R7KjL>5>0Y=E0%Kh~Pd|li$A?o}fnAh;NpV`{ z6vZ^9)fqPCI#i;>#hJ{yB!T>{7B`j(2BPl-=`&LO)a9=y*Asua&U;~*TyDfxmU(`$ zC^qpQRlt$;n%#tBTDWD~KJR7l&eOVJnzm`AY_973{%VGS2NX5vFFf46Yo{30d;KP} zbi(t@>IbyBk8pmF^qlG9R#}ub-QdZWG}^`NAn@>}g$uOl%bD4!dj91~8XNJu-p(H( z>8*k?_k7okucX-T)_ZTIM6+E1H7MgClf`tuIey*QH!Q6Su>W=Kae)ORN;YGJH|0I1 zq+6=owrD{6hA_wIu5?opnZP5R%DWxc{YLvam?lO34r=B$$L;GEbqb2$<=i0GlRnj! zaqdb1JVU63aVx(xzQXu-s5I<#dX`+wjYzu&WU7Zpy(=~c`fvH3?Ibp%K1=m5U3o9u zIPh(8N|XM56$k+o`8*fw(8)6Oi_)cC?EYe=zHxEx(;5GYbZU##G(vFpx@T6yBp%gif)umsO-zFX%2mfnf)2(MSu#eFozo zwR2*={Of4h!IS;hLu4w^Cn{-Zi<<_2 zOlV1Ln=?ZQ>u+rI4apwIZwVFH4`pvvZYE#yj8uoH+#lG-wj}wsP*Ev=pmNE=!PPH| z$p(q6KBuyxEw09)M3iK|NB5^CrKU}OwVD?He5d!`qg3J(Go2Zsr<7os?w#a=ZU8+q zsa>-Aus8=QuX6sNU?BKUPj(C?^I)IrXzQc(3&@*uU@YV*0$(OUV;HT+#2sUvjqV4Hb%PG-?SPAi(Jj-ITL~=4Ssc_&q(l^qWRe!3uAu78<|i+A@36Sg!a&# zA#0V>)R*s~tWMYjnWLOx-~Jb`!X75qC?e0RG24irAzG&n?ZFo&f9_+HCsJG#B- zJbZH4${a8#wf9k1o0~eGTAkGBCkxtXNNlyW0`RX6`IgZVN?uy2({3FYMQ+gFiI&t9 z1K+nY+=N*(L+}KZ$640bz=g=8`>q@SnA?LUKxbj4R&dh51gAHw!6wx|)OnacXlQ<3 z>n*5CbS zIVfK~amH%b5nsAy@|MJ?A`F2(VS z@5n!$V)_B((@Ve8`YhAvT^s#6wHUaH4|wrsVSY(#*WzZKiMg>Yhwe31Dh4I)XM}G<%7A1a9>$fX4q;3s|!cw z!gcRl@7%4jNI>1st?QQ8JW=Eag*N)-B)e5#!N4xCi~@~;ECfU}2b`5Et? z`7IFVRFx=ix)uCUdfIrwjG~c#1g*J3qr^Z5yq2*6m_U^eTK`n8bb2%ZWnFf(rBhhE z1?I(92=3+LU|6P|N985tOMxT9pH)-%`S`iVfjn1@8$Ra zZ*!QL?$u7%n+t9G3ciQns(2)vh4eVXJ7AQFA~F|o)%!m^*V16tR9~%7PHrI_d8PHXxG}n%;wa}v7k@)& z_h!@K%7-tPe;PNX(3(v;CRgSleL)ZhzAScwgSVXOu0hA20*`C3B~^=&T$Hb&(GZPq zg7vMHZm9DphJ`qKCQ6pc znGz}C=kwdDDSo7zuWBAVa4Go-ZgZt^<%`GJK5R0q9g0+;aeEeh7{%3aM9<3o7Q=ml~O z^3cwI`X+NjV#ON{uzSFpr|1dlGwOD3VZXKCCJ+M~Kvs{gRpx!5H+QHCM=mr{iJYK% zRo4N#O_Wq42d$4MT?U@Y_Z({$-P>Y$5P;-hk^Db)@89r;DV|W-mA^VZzJB?ObSt07 zQPM5wji5M~3NW0On!o(o(QY(l+L!O4P`~vVylY2#COT^$q%~HkXB|R@2vz9z$X@)9n4?<(olY6XkFR+Zn zc`83WtourcRws5qBbm2i+xYgSy^mJn(IM*c@!);qluL~cuxrZm&r-ClgqUq=@)h^z zsPX>LKYwB;DEfXi+manLEktjwr1-VLDny7*^&qPb+DJAu+_e`dpmRRCP<95?&N15) zx2qt!BAROm6%zw*Spq)BJOGiF?RyP3%hr@o-|;J=G6E*NqJcxEI@OghRu*4t&E*Pw=rW_)>3*3@wm1<5%`Q+9|ZzPtPPA0Ye39;*4Hk74^o{C>gCN4-ZH%n z6SrKfG)inY_hSBO+pKSddvR02tVYWLR!Q?c{$6c!qjqgDc9^s^Ld{axor-wOb52vldC1|0eWaaHsP)~sj-4Cyy2_AR|9sCnL<3#kS_hFUfpzRu-;>7YWDYJ za?cI1N;jPI@Nqxzl9lXxvd>H5^bHHBy?ML+)^ek(9g^AiY(*B!e7nq6&3LoUwC{u( zYzsgw|EXmgl-sT$&T}-XgREEYb9A$WtbxD|X*(Vw-2IItvMHy*2i@k}cF5ugkNdZ{ z;U@H3GYOaYS$V_%{Jw${^b*sUE?%_ReDtYL)KH>gK_Kq(G3w__B8!h+dAf^qXv&!#E4J@9DR_1E$#g3nu7LyVI2R!w zdb-(Scaq$2D{JeC9p!pUJL-$|kiWy%p#id&8~)A$*|>@7L?`jT)5f24{U7x$w572x~(p<>r-~n9z)MC|=$lP8+Y@nnrmqQCZ zlV1gUZ2W*AbL-fIbMZ~e(aeb+7hJf^XY^TadahC>4mULdP5}1o2wTx!ZLZyW{DpM9 zS%Q8zOE4Mt?RrxR=hk~IRz)0=+0n$lkFT>>77c#PKEa?4>xE}Pr~LeugPistaktnr z7?>#BPiRto7V|dCJVXP3PkD1Qu>rf|{9W7p-rsV&@f0PJ3bbsFg{oMAKv#<*yhZHR zHlPg6Dmrr7_E3NtWI@SFE^yQQ7$T}ljhNNDohD~eL(rJLA85gS<|PGkGy??|&nfhb z6fUjnaqs@~n@!A|j!0eEMIYt+yQLHEm^iq2@I3ngjoW2;os2fO4I#_#%C^1RA}l6c zv7@TZ!PMNUw;Bp^=qtuX1gliWhXuR9F06*tS(YgbkR?0XoXYp6*k6d5C*o!`%DZj zshk*j>G}ivsGyf8%W|&@){VS^YmH2TB7T4;GS9E8Vn3+c@XU-%h{E+eqhQQDIcEz0 zx!gy!xK9WsKPoH6RY4P)E^QXKyo{l%lroLibm33cw8OU_LjW|_0K0lY1O<1qK@HW3E!wD?;{)sH$XIyEctRCh?18eHg>mJlK zNH8lvHk++lT;u{_&xH%$8vsl9e2!cbr7wV>WJk(^UQ8JH0+-uHYb=`O*wb zDI$u^UIJJ)yYb0&H1T-v@j>H{(R5y(Wn@)xbjU@KhP54O@8+rM+ay}3)?a+ z@*go7AFBSI29X@c$4Z=Wyejphn66=m3dwnoqCemareD5S3Tcgdnk04xeg~Qb-b?bw zU&-2m)IRANX7zIigcm$tq~a~>1n>^5S0yWo9jJW1fe-lg{-V>tpV`o)v{dI~S5vcQ zZ|J)p_eyU69y)OdV@bvEZ1!iN{8&caQe!WaVC?{A)&cNWFEr-gV8=VJ8~uOMQ+xF{Gg^FEX*|$`; z^@~xBL!Y}Q?E_L|t0T(uGwQs&2vd~cwKa-6ILGC_xdXtS-h8V4T{Wr6?b7`{U7A5& zyn&_Wu0-?kAD*fHq15mFSrn_RnDi~@ni2S~fW*HVHqBr6t3zf5WEHnYiop|QZ%@wI zWpn?!$V9^T0qIz;&o+;j}2qV)fsnQ(?+iMu^={Uou=0c^x^|CheH~0dcPfa`?eUC*LP7hCA7$3G< zH4Hq;dJ%0m-~;c^-6~M5h(VH6I`|tq$A;v`Cx_0OL_7cU^7?mY+}9l3W~4p6uOUp7 zr4POZd5RKyJv8PWO=c0m43RMK)50e9ojmlU8ok9{bsYsD()@SZ;}jlYnZ~cB1>orR zaM~nWRhNB(GRXz}EC=gJ?`?Dn@ECQv3#74k4aEwi{CkpJo46W*&J3Zbr{%09O{Pa% zNt)c+{+~O>2o*x72NaLt0KStzr`5lGdcmhjZ6+U&>Lax|FF#b4%#-m#DX_{Nj}k}( zuql5HxICv!&JkwDrD64E(#Kb?)-9%-uuiFN;>3%S94~@JD@CPFhrBA&G3w3PUNZ8Y$P+L zrZb0EyO4`MC~+vF)%81d{{Uhiy#ZGUiW>|A?Ht?1BvOqF)D41{%R$hb=+R7GB`8;1 z;F5*|jdRkvj^%9PNcSio`1Db-!i@B?fxE8Tec?F0`i?GIMazV(C)p#YOPouGlHq3w zoi0RIZ#lT!IY8&MAKL*vlm7yVFtdy%7SNo+X{#@RIF; zzzIrT^$H3*80H!Qf*$uMb;+W*1z9A=*1Vv93#MZ9CU*Ch1-NYB4)N4i-6SrTZuP*P zSXD~xXs;XQ`6`!BmXcBr+*PlY4%av%%BV?>f3b$0H;ipSSCS-tl#wv=O4=3!=pxO4 zS!pk&DS%kZWhv!sr8O3lcsS<%R6{fi;3ecRNDRY0m#^rFTN&b_OnyB^ER#NE7|2Eg z7>0_pOPwXJ>22V~OE|g&*c48S_DAgcZ?!nw=!w>Zm918KaRLq9UN9z01O@An=QEvV^(Sf$mb)|+$D|D=aAh-mzi29l zybr?B0NP)GL1w$g+Xy&ULDHelv%_< z-jhPG&G-X&~I8x5O-#zu&DoQ)~t)iHOau6_GTfvd6vH%5InQfzmaHNI8rj2 zL^DHc5+x0yiCX*M?M>YpH-*j}V+81UCqH_xvL4mF3CrI!8Y@hEbq3oQ8h|=PyK~~_ z`@D?nn{{nLvzbdd@%NREhQ>EGMP%1dbw+<;b1#b0i%Y3XnpNPjmj`v#8Sgm$t@%R_ zttwKE$PUZ_~X!b}X6ICI#!XyA6E>?GWypvQGF zCy2@V{(Q)QtObegry<)E$9SSDr5rkfI8?10#Y|L@J+o&%1uT(B-Q8moBsAwy_kjTX z>n*Mw#m2IhbtX1UPPWalZ*xR|fx?*=H0NZH)Fa6U^0Hl4pK)9oDhJ~Vh(t1x%eZm7 zxJU0*Az@85r5xH|*{SrB<7jHUDy(!M=FiA3IN^bZky*ZNNin=^@5FMcK}-ib6Bu%F Q3>l#IQu{^ebE}X42RZYZ-T(jq literal 0 HcmV?d00001 diff --git a/presets/archipack_floor/tiles_l+s_30x30_15x15.py b/presets/archipack_floor/tiles_l+s_30x30_15x15.py new file mode 100644 index 0000000..bbcc8b2 --- /dev/null +++ b/presets/archipack_floor/tiles_l+s_30x30_15x15.py @@ -0,0 +1,34 @@ +import bpy +d = bpy.context.active_object.data.archipack_floor[0] + +d.b_width = 0.20000000298023224 +d.width_vary = 50.0 +d.t_width_s = 0.20000000298023224 +d.is_grout = True +d.tile_types = '2' +d.space_l = 0.004999999888241291 +d.is_length_vary = False +d.hb_direction = '1' +d.offset_vary = 50.0 +d.offset = 50.0 +d.spacing = 0.004999999888241291 +d.thickness = 0.10000000149011612 +d.bevel_res = 1 +d.is_offset = False +d.grout_depth = 0.0010000003967434168 +d.t_width = 0.30000001192092896 +d.is_ran_thickness = False +d.is_mat_vary = False +d.is_random_offset = False +d.space_w = 0.004999999888241291 +d.is_bevel = True +d.ran_thickness = 50.0 +d.max_boards = 2 +d.t_length = 0.30000001192092896 +d.b_length_s = 2.0 +d.bevel_amo = 0.001500000013038516 +d.is_width_vary = False +d.num_boards = 4 +d.length_vary = 50.0 +d.b_length = 0.800000011920929 +d.mat_vary = 1 From dc4197a53564b8f02fe6e429576ba849d4db9905 Mon Sep 17 00:00:00 2001 From: s-leger Date: Wed, 21 Jun 2017 14:53:06 +0200 Subject: [PATCH 12/17] [FEATURE] Floor --- __init__.py | 55 +- archipack_floor.py | 1191 ++++++++++++++++++++++++++++++++++++++++++++ bmesh_utils.py | 130 +++++ materialutils.py | 27 +- 4 files changed, 1387 insertions(+), 16 deletions(-) create mode 100644 archipack_floor.py diff --git a/__init__.py b/__init__.py index d4d93ea..ab53e05 100644 --- a/__init__.py +++ b/__init__.py @@ -55,10 +55,12 @@ imp.reload(archipack_stair) imp.reload(archipack_wall) imp.reload(archipack_wall2) - imp.reload(archipack_roof2d) + # imp.reload(archipack_roof2d) imp.reload(archipack_slab) imp.reload(archipack_fence) imp.reload(archipack_truss) + # imp.reload(archipack_toolkit) + imp.reload(archipack_floor) imp.reload(archipack_rendering) imp.reload(addon_updater_ops) try: @@ -79,10 +81,12 @@ from . import archipack_stair from . import archipack_wall from . import archipack_wall2 - from . import archipack_roof2d + # from . import archipack_roof2d from . import archipack_slab from . import archipack_fence from . import archipack_truss + # from . import archipack_toolkit + from . import archipack_floor from . import archipack_rendering from . import addon_updater_ops try: @@ -266,7 +270,8 @@ class Archipack_Pref(AddonPreferences): def draw(self, context): layout = self.layout - row = layout.row() + box = layout.box() + row = box.row() col = row.column() col.label(text="Tab Category:") col.prop(self, "tools_category") @@ -462,23 +467,24 @@ def draw(self, context): box = row.box() box.label("Objects") row = box.row(align=True) - col = row.column() - subrow = col.row(align=True) - subrow.operator("archipack.window_preset_menu", + # col = row.column() + # subrow = col.row(align=True) + row.operator("archipack.window_preset_menu", text="Window", icon_value=icons["window"].icon_id ).preset_operator = "archipack.window" - subrow.operator("archipack.window_preset_menu", + row.operator("archipack.window_preset_menu", text="", icon='GREASEPENCIL' ).preset_operator = "archipack.window_draw" - col = row.column() - subrow = col.row(align=True) - subrow.operator("archipack.door_preset_menu", + # col = row.column() + # subrow = col.row(align=True) + row = box.row(align=True) + row.operator("archipack.door_preset_menu", text="Door", icon_value=icons["door"].icon_id ).preset_operator = "archipack.door" - subrow.operator("archipack.door_preset_menu", + row.operator("archipack.door_preset_menu", text="", icon='GREASEPENCIL' ).preset_operator = "archipack.door_draw" @@ -521,9 +527,20 @@ def draw(self, context): ).ceiling = True addon_updater_ops.update_notice_box_ui(self, context) + # row = box.row(align=True) # row.operator("archipack.roof", icon='CURVE_DATA') - + + # toolkit + # row = box.row(align=True) + # row.operator("archipack.myobject") + + row = box.row(align=True) + row.operator("archipack.floor_preset_menu", + text="Floor", + # icon_value=icons["floor"].icon_id + ).preset_operator = "archipack.floor" + # ---------------------------------------------------- # ALT + A menu # ---------------------------------------------------- @@ -559,6 +576,10 @@ def menu_func(self, context): text="Truss", icon_value=icons["truss"].icon_id ) + layout.operator("archipack.floor_preset_menu", + text="Floor", + # icon_value=icons["floor"].icon_id + ) # ---------------------------------------------------- @@ -598,10 +619,12 @@ def register(): archipack_stair.register() archipack_wall.register() archipack_wall2.register() - archipack_roof2d.register() + # archipack_roof2d.register() archipack_slab.register() archipack_fence.register() archipack_truss.register() + # archipack_toolkit.register() + archipack_floor.register() archipack_rendering.register() if HAS_POLYLIB: @@ -635,10 +658,12 @@ def unregister(): archipack_stair.unregister() archipack_wall.unregister() archipack_wall2.unregister() - archipack_roof2d.unregister() + # archipack_roof2d.unregister() archipack_slab.unregister() archipack_fence.unregister() archipack_truss.unregister() + # archipack_toolkit.unregister() + archipack_floor.unregister() archipack_rendering.unregister() if HAS_POLYLIB: @@ -651,7 +676,7 @@ def unregister(): previews.remove(icons) icons_collection.clear() - addon_updater_ops.unregister(bl_info) + addon_updater_ops.unregister() # bpy.utils.unregister_module(__name__) diff --git a/archipack_floor.py b/archipack_floor.py new file mode 100644 index 0000000..0233800 --- /dev/null +++ b/archipack_floor.py @@ -0,0 +1,1191 @@ +# This file is part of JARCH Vis +# +# JARCH Vis 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 3 of the License, or +# (at your option) any later version. +# +# JARCH Vis 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 JARCH Vis. If not, see . + +import bpy +from bpy.types import Operator, PropertyGroup, Mesh, Panel +from bpy.props import ( + BoolProperty, EnumProperty, FloatProperty, + IntProperty, CollectionProperty + ) +from random import uniform, randint +from math import tan, pi, sqrt +from mathutils import Vector +from .bmesh_utils import BmeshEdit as bmed +from .archipack_manipulator import Manipulable +from .archipack_preset import ArchipackPreset, PresetMenuOperator +from .archipack_object import ArchipackCreateTool, ArchipackObject + + +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 + + +def tile_ls(ow, ol, tw, tl, s, z): + """ + pattern: + _____ + | |_| + |___| + + x and y are axis of big one + """ + + verts = [] + faces = [] + + # 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) + + # small, offset from big x,y + xo = 0.75 * tw + yo = 0.25 * tl + + # offset for pattern + rx = 2.5 * tw + ry = 0.5 * tl + + # 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 + + while y < ol_y: + + while x < ow_x: + + p = len(verts) + + # 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: + + 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) + + if x1 > 0 and x0 < ow and y0 < ol: + + 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)) + + x += rx + + y += ry + x = x % rx - tw + if x < -tw: + x += rx + + return verts, faces + + +def tile_hexagon(ow, ol, tw, s, z): + verts = [] + faces = [] + offset = False + + w = tw / 2 + y = 0.0 + h = w * tan(pi / 6) + r = sqrt((w * w) + (h * h)) + + while y < ol + tw: + if not offset: + x = 0.0 + else: + x = w + (s / 2) + + while x < ow + tw: + p = len(verts) + + 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)]) + + x += tw + s + + y += r + h + s + offset = not offset + + return verts, faces + + +def tile_lms(ow, ol, tw, s, z): + verts = [] + faces = [] + small = True + + y = 0.0 + ref = (tw - s) / 2 + + 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 + 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 + + +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 y < ol: + + 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) + + while x < ow: + p = len(verts) + x0 = max(0, x) + x1 = min(ow, x + tw) + + 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 + + y += tl + s + off = not off + + return verts, faces + + +def wood_parquet(ow, ol, bw, s, num_boards, z): + verts = [] + faces = [] + x = 0.0 + + start_orient_length = True + + # figure board length + bl = (bw * num_boards) + (s * (num_boards - 1)) + while x < ow: + + y = 0.0 + + orient_length = start_orient_length + + while y < ol: + + if orient_length: + y0 = y + y1 = min(y + bl, ol) + + for i in range(num_boards): + + bx = x + i * (bw + s) + + if bx < ow and y < ol: + + # make sure board should be placed + x0 = bx + x1 = min(bx + bw, ow) + + 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)) + + else: + x0 = x + x1 = min(x + bl, ow) + + for i in range(num_boards): + + by = y + i * (bw + s) + + if x < ow and by < ol: + y0 = by + y1 = min(by + bw, ol) + 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)) + + y += bl + s + + orient_length = not orient_length + + start_orient_length = not start_orient_length + + x += bl + s + + return verts, faces + + +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 is_width_vary: + v = bw * (width_vary / 100) * 0.499 + bw2 = uniform(bw / 2 - v, bw / 2 + v) + else: + bw2 = bw + + 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) + else: + y = -(row % 2) * bl * (offset / 100) + else: + y = 0 + + row += 1 + counter = 1 + + while y < ol: + + z = th + + if is_r_h: + v = z * 0.5 * (r_h / 100) + z = uniform(z / 2 - v, z / 2 + v) + + 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) + + y0 = max(0, y) + y1 = min(y + bl2, ol) + + if y1 > y0: + p = len(verts) + + verts.extend([(x, y0, z), (x1, y0, z), (x1, y1, z), (x, y1, z)]) + faces.append((p, p + 1, p + 2, p + 3)) + + y += bl2 + s_l + + counter += 1 + + x += bw2 + s_w + + return verts, faces + + +def tile_grout(ow, ol, depth, th): + z = min(th - 0.001, max(0.001, th - depth)) + x = ow + y = ol + + 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)] + + 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)] + + return verts, faces + + +def update(self, context): + self.update(context) + + +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) + 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( + name="Vary Material?", + default=False, + description="Vary Material indexes", + update=update) + mat_vary = IntProperty( + name="#variations", + min=1, + max=10, + default=1, + description="Material index maxi", + update=update) + auto_update = BoolProperty( + options={'SKIP_SAVE'}, + default=True, + update=update + ) + + 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 + s = self.manipulators.add() + s.prop1_name = "over_length" + # s.prop2_name = "y" + s.type_key = 'SIZE' + + def update(self, context): + + o = self.find_in_selection(context, self.auto_update) + + if o is None: + return + + self.setup_manipulators() + + 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 + if 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 + + if self.is_grout: + th = min(self.grout_depth + bevel, self.thickness - 0.001) + else: + th = self.thickness + + bmed.solidify(context, o, th) + + # 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)]) + + self.restore_context(context) + + +class ARCHIPACK_PT_floor(Panel): + bl_idname = "ARCHIPACK_PT_floor" + bl_label = "Flooring" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "Archipack" + + @classmethod + def poll(cls, context): + # ensure your object panel only show when active object is the right one + return archipack_floor.filter(context.active_object) + + def draw(self, context): + o = context.active_object + if not archipack_floor.filter(o): + return + layout = self.layout + + # retrieve datablock of your object + props = archipack_floor.datablock(o) + + # Manipulate mode operator + layout.operator('archipack.floor_manipulate', icon='HAND') + + box = layout.box() + row = box.row(align=True) + + # Presets operators + row.operator("archipack.floor_preset_menu", + text=bpy.types.ARCHIPACK_OT_floor_preset_menu.bl_label) + row.operator("archipack.floor_preset", + text="", + icon='ZOOMIN') + row.operator("archipack.floor_preset", + text="", + icon='ZOOMOUT').remove_active = True + + layout.prop(props, "tile_types", icon="OBJECT_DATA") + + layout.separator() + + layout.prop(props, "over_width") + layout.prop(props, "over_length") + layout.separator() + + # width and lengths + layout.prop(props, "thickness") + + type = int(props.tile_types) + + 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") + else: + layout.prop(props, "spacing") + + # Grout + layout.separator() + layout.prop(props, "is_grout", icon="OBJECT_DATA") + if props.is_grout: + layout.prop(props, "grout_depth") + + # 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") + + # 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") + layout.separator() + + layout.separator() + layout.prop(props, "is_mat_vary", icon="MATERIAL") + if props.is_mat_vary: + layout.prop(props, "mat_vary") + + """ + layout.prop(props, "is_unwrap", icon="GROUP_UVS") + if props.is_unwrap: + layout.prop(props, "is_random_uv", icon="RNDCURVE") + layout.separator() + + if context.scene.render.engine == "CYCLES": + layout.prop(props, "is_material", icon="MATERIAL") + else: + layout.label("Materials Only Supported With Cycles", icon="POTATO") + + if props.is_material and context.scene.render.engine == "CYCLES": + layout.separator() + layout.prop(props, "col_image", icon="COLOR") + layout.prop(props, "is_bump", icon="SMOOTHCURVE") + + if props.is_bump: + layout.prop(props, "norm_image", icon="TEXTURE") + layout.prop(props, "bump_amo") + layout.prop(props, "im_scale", icon="MAN_SCALE") + layout.prop(props, "is_rotate", icon="MAN_ROT") + + if props.flooring_types == "2": + layout.separator() + layout.prop(props, "mortar_color", icon="COLOR") + layout.prop(props, "mortar_bump") + + layout.separator() + layout.operator("mesh.flooring_materials", icon="MATERIAL") + layout.separator() + layout.prop(props, "is_preview", icon="SCENE") + """ + + +class ARCHIPACK_OT_floor(ArchipackCreateTool, Operator): + bl_idname = "archipack.floor" + bl_label = "Floor" + bl_description = "Create Floor" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + def create(self, context): + + # Create an empty mesh datablock + 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 + 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 + + def execute(self, context): + if context.mode == "OBJECT": + bpy.ops.object.select_all(action="DESELECT") + o = self.create(context) + o.location = bpy.context.scene.cursor_location + o.select = True + context.scene.objects.active = o + + # Start manipulate mode + self.manipulate() + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_floor_preset_menu(PresetMenuOperator, Operator): + bl_idname = "archipack.floor_preset_menu" + bl_label = "Floor preset" + preset_subdir = "archipack_floor" + + +class ARCHIPACK_OT_floor_preset(ArchipackPreset, Operator): + """Add a Floor Preset""" + bl_idname = "archipack.floor_preset" + bl_label = "Add Floor preset" + preset_menu = "ARCHIPACK_OT_floor_preset_menu" + + @property + def blacklist(self): + return ['manipulators', 'over_length', 'over_width'] + + +class ARCHIPACK_OT_floor_manipulate(Operator): + bl_idname = "archipack.floor_manipulate" + bl_label = "Manipulate" + bl_description = "Manipulate" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_floor.filter(context.active_object) + + def invoke(self, context, event): + d = archipack_floor.datablock(context.active_object) + d.manipulable_invoke(context) + return {'FINISHED'} + + +def register(): + bpy.utils.register_class(archipack_floor) + Mesh.archipack_floor = CollectionProperty(type=archipack_floor) + bpy.utils.register_class(ARCHIPACK_PT_floor) + bpy.utils.register_class(ARCHIPACK_OT_floor) + bpy.utils.register_class(ARCHIPACK_OT_floor_preset_menu) + bpy.utils.register_class(ARCHIPACK_OT_floor_preset) + bpy.utils.register_class(ARCHIPACK_OT_floor_manipulate) + + +def unregister(): + bpy.utils.unregister_class(archipack_floor) + del Mesh.archipack_floor + bpy.utils.unregister_class(ARCHIPACK_PT_floor) + bpy.utils.unregister_class(ARCHIPACK_OT_floor) + bpy.utils.unregister_class(ARCHIPACK_OT_floor_preset_menu) + bpy.utils.unregister_class(ARCHIPACK_OT_floor_preset) + bpy.utils.unregister_class(ARCHIPACK_OT_floor_manipulate) diff --git a/bmesh_utils.py b/bmesh_utils.py index fe5e3ef..3ad3255 100644 --- a/bmesh_utils.py +++ b/bmesh_utils.py @@ -103,6 +103,136 @@ def buildmesh(context, o, verts, faces, matids=None, uvs=None, weld=False, clean bpy.ops.mesh.delete_loose() bpy.ops.object.mode_set(mode='OBJECT') + @staticmethod + def addmesh(context, o, verts, faces, matids=None, uvs=None, weld=False, clean=False, auto_smooth=True): + bm = BmeshEdit._start(context, o) + nv = len(bm.verts) + nf = len(bm.faces) + + for v in verts: + bm.verts.new(v) + + bm.verts.ensure_lookup_table() + + for f in faces: + bm.faces.new([bm.verts[nv + i] for i in f]) + + bm.faces.ensure_lookup_table() + + if matids is not None: + for i, matid in enumerate(matids): + bm.faces[nf + i].material_index = matid + + if uvs is not None: + layer = bm.loops.layers.uv.verify() + l_i = len(uvs) + for i, face in enumerate(bm.faces[nf:]): + l_j = len(uvs[i]) + for j, loop in enumerate(face.loops): + loop[layer].uv = uvs[i][j] + + if weld: + bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001) + BmeshEdit._end(bm, o) + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + if auto_smooth: + bpy.ops.mesh.faces_shade_smooth() + o.data.use_auto_smooth = True + else: + bpy.ops.mesh.faces_shade_flat() + if clean: + bpy.ops.mesh.delete_loose() + bpy.ops.object.mode_set(mode='OBJECT') + + """ +from archipack.bmesh_utils import BmeshEdit as bmed +bmed.solidify(C, C.active_object, 0.1) +bmed.bevel(C, C.active_object, 0.002) + """ + + @staticmethod + def bevel(context, o, + offset, + offset_type=0, + segments=1, + profile=0.5, + vertex_only=False, + clamp_overlap=True, + material=-1, + use_selection=True): + """ + /* Bevel offset_type slot values */ + enum { + BEVEL_AMT_OFFSET, + BEVEL_AMT_WIDTH, + BEVEL_AMT_DEPTH, + BEVEL_AMT_PERCENT + }; + """ + bm = bmesh.new() + bm.from_mesh(o.data) + bm.verts.ensure_lookup_table() + if use_selection: + geom = [v for v in bm.verts if v.select] + geom.extend([ed for ed in bm.edges if ed.select]) + else: + geom = bm.verts[:] + geom.extend(bm.edges[:]) + + bmesh.ops.bevel(bm, + geom=geom, + offset=offset, + offset_type=offset_type, + segments=segments, + profile=profile, + vertex_only=vertex_only, + clamp_overlap=clamp_overlap, + material=material) + + bm.to_mesh(o.data) + bm.free() + + @staticmethod + def bissect(context, o, + plane_co, + plane_no, + dist=0.001, + use_snap_center=False, + clear_outer=True, + clear_inner=False + ): + + bm = bmesh.new() + bm.from_mesh(o.data) + bm.verts.ensure_lookup_table() + geom = bm.verts[:] + geom.extend(bm.edges[:]) + geom.extend(bm.faces[:]) + + bmesh.ops.bisect_plane(bm, + geom=geom, + dist=dist, + plane_co=plane_co, + plane_no=plane_no, + use_snap_center=False, + clear_outer=clear_outer, + clear_inner=clear_inner + ) + + bm.to_mesh(o.data) + bm.free() + + @staticmethod + def solidify(context, o, amt): + bm = bmesh.new() + bm.from_mesh(o.data) + bm.verts.ensure_lookup_table() + geom = bm.faces[:] + bmesh.ops.solidify(bm, geom=geom, thickness=amt) + bm.to_mesh(o.data) + bm.free() + @staticmethod def verts(context, o, verts): """ diff --git a/materialutils.py b/materialutils.py index 76159fa..7cb48ac 100644 --- a/materialutils.py +++ b/materialutils.py @@ -105,7 +105,32 @@ def add_fence_materials(obj): obj.data.materials.append(wood_mat) obj.data.materials.append(metal_mat) obj.data.materials.append(glass_mat) - + + @staticmethod + def add_floor_materials(obj): + con_mat = MaterialUtils.build_default_mat('Floor_grout', (0.5, 0.5, 0.5)) + alt1_mat = MaterialUtils.build_default_mat('Floor_alt1', (0.5, 1.0, 1.0)) + alt2_mat = MaterialUtils.build_default_mat('Floor_alt2', (1.0, 1.0, 1.0)) + alt3_mat = MaterialUtils.build_default_mat('Floor_alt3', (0.28, 0.2, 0.1)) + alt4_mat = MaterialUtils.build_default_mat('Floor_alt4', (0.5, 1.0, 1.0)) + alt5_mat = MaterialUtils.build_default_mat('Floor_alt5', (1.0, 1.0, 0.5)) + alt6_mat = MaterialUtils.build_default_mat('Floor_alt6', (0.28, 0.5, 0.1)) + alt7_mat = MaterialUtils.build_default_mat('Floor_alt7', (0.5, 1.0, 0.5)) + alt8_mat = MaterialUtils.build_default_mat('Floor_alt8', (1.0, 0.2, 1.0)) + alt9_mat = MaterialUtils.build_default_mat('Floor_alt9', (0.28, 0.2, 0.5)) + alt10_mat = MaterialUtils.build_default_mat('Floor_alt10', (0.5, 0.2, 0.1)) + obj.data.materials.append(con_mat) + obj.data.materials.append(alt1_mat) + obj.data.materials.append(alt2_mat) + obj.data.materials.append(alt3_mat) + obj.data.materials.append(alt4_mat) + obj.data.materials.append(alt5_mat) + obj.data.materials.append(alt6_mat) + obj.data.materials.append(alt7_mat) + obj.data.materials.append(alt8_mat) + obj.data.materials.append(alt9_mat) + obj.data.materials.append(alt10_mat) + @staticmethod def add_handle_materials(obj): metal_mat = MaterialUtils.build_default_mat('metal', (0.4, 0.4, 0.4)) From 5401c75eef436e5a3018c148df3d5a0e4f270e96 Mon Sep 17 00:00:00 2001 From: s-leger Date: Thu, 22 Jun 2017 17:15:53 +0200 Subject: [PATCH 13/17] [FEATURE] FeedbackPanel title layout (nBurn fix) --- archipack_gl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archipack_gl.py b/archipack_gl.py index 8201b8d..f500432 100644 --- a/archipack_gl.py +++ b/archipack_gl.py @@ -1044,7 +1044,7 @@ def draw(self, context, render=False): """ x_min, x_max, y_min, y_max = self.screen.size(context) available_w = x_max - x_min - 2 * self.spacing.x - main_title_size = self.main_title.text_size(context) + main_title_size = self.main_title.text_size(context) + Vector((5, 0)) # h = context.region.height # 0,0 = bottom left From 2840a80b2f7534780bec220a05370b7e04f1ced9 Mon Sep 17 00:00:00 2001 From: s-leger Date: Fri, 23 Jun 2017 16:40:03 +0200 Subject: [PATCH 14/17] [FEATURE] Floor icon --- icons/archipack.png | Bin 0 -> 1364 bytes icons/floor.png | Bin 0 -> 1457 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 icons/archipack.png create mode 100644 icons/floor.png diff --git a/icons/archipack.png b/icons/archipack.png new file mode 100644 index 0000000000000000000000000000000000000000..92503c824dbba4bca5148055310822c93ef66fec GIT binary patch literal 1364 zcmV-a1*`grP)RZ1mQ_U zK~z}7?UqewQ&|+pfA@X#B`+^&QqxpS8=}SH*Fx*pAXWzloWKYcO;+MUT(}Vhq0mK_ z4z2|Eih?_l;6etva3Mmav@WWF8LL!;erQZvoThF1Uh;Bh(I%5lty*Us7{&vS$Gz{} z`#<-8&bj9g-|UA@sr79ICJI<4<0dAg6bk6N&aq?1xOeX!KwJRw_U+qPmW8hCbaZr3UthoGw9n`BWHK4Ly1MZD{hU2} z7Q-;Od-pDH-n`-c`}d5Ejgie}OTb7Xk%;y7_Ikw<9Y62hz5Dw^hYo$$*4CzO*|LRb zG)f>4z~}Q}S=L9vv)L@^beikeud{RKPEx59u~=-egrwy0B19q)qR}X-s^T~fIzN2oyyjm&@_==~FITxUlU0slABgzJ2?C2kPhM=CEy>d_GU9R3Z=v z5Rb>%v111u#)#34Ya9tPMwow!XAr|peb>$VX%mH23%S}y9IF3Vqe?QaH)3mg-5DJ9|hr_t8 z%k1ndb8~a__xE$;$PtboKTd0FE3WHS>ArveK0cq1g9i^*PcEfI)3lG@gzw(H%j>#M zAQ0fyt5=MUjxszvOhZEh;c%E>Fi2x#BhAgt?Ao=9Q>RX`apOkHu3h8& z`SbMk^;PpgO8LRluV5gT%Z;h38dp`7P$)z`pJ!@n3ez<4dc72jMGA!iGcz*;gFym; zKsDACfW3S7Rzp#n1Lbo0(+o^bPQIzDtHX6&Iy*awL>Ak^)YKG)Vc_%m2nK`1Vlf&U z8&OpiAw(5i*L4O52a!_ZI8OC@6--e+&OkPs?VXsI__4XUIUt1K+_`fU3I+1{{EEef zVW6t&vLusIE@PlFuj@KO2p&CpG!8^pb3g#{)~#D?-n{vjWHR|vu~-Bk5D4J9?lQ0n zwUu$@yH|-|q)t{rdGRfvX=9K%Y`X65!IMOC?Ryv`UW`LSULEsZ4NR{rw>3tv;jMk)g1_3PKQde8W`6?NxoLi3ybr|b`% Wjd;PUs~hM50000o literal 0 HcmV?d00001 diff --git a/icons/floor.png b/icons/floor.png new file mode 100644 index 0000000000000000000000000000000000000000..1590c335ae63191921ed621d2f8844ded23c8230 GIT binary patch literal 1457 zcmV;i1y1^jP)A7fiva`xd@V^*kw&9& z=jG-7x?;tOaR6@vke(O#H2^)5Bn>N-O2@Ws+dgP)YebEfx#6ZQs72 z(P*Z%TJ1LgasZ&Qv2jUY1Ary~cV$`r^vIDTZimBhN3Ykjx3`zQy}i72=@Nwy6hcs^ z(=Dz`N=oQ*x$bMV+IV_;x*LG$pA?b`;5`6SX=!PmQ>RW{5kka_MkD*LT_Z_mW@b_d z!KS7r#$qv+mX3-zq3bs8p)^dcFQAfR_Lu9*+YLUHb-rk0nWptJUf|g@uJX-aT{( z-wzET9*-jw3gPnQ%a0Ykd-rbS<>g^wVgiyRAtfaRi9`Z3b90a-2|@^XJRSst!NomU zSy>n#AD@XtBKI;fG8zJbKzp+D*8#{yMMd*gt93_laWRI5hLDw&h4t&#KLH1Ta5xN` z%?6vzhAmsRpuN2vk|d#|s0drPZbf`%<}o+`+_-TA2E*!9x7+=4I2=9+pclZa$%o~b zN~QX(rly9Km6a4i&}1_4_U+r8oth*f+^?IH;bA}|jdGqE)f3&u?k|f8*#%QlpxhK2@uy`)vH%2gkW=XGkbb^*xlVtkH?J}UmtuvA1W#;(An7u0GOSbfz#>4nl)?S zbUK5F4*O%HF+upXxo3034NNIZ{q`;hllUw=H~VR7zOa=h7B8*HVDjSGXP`& z_`z&8hwAF;7Mhxxc;Ui@#g2!Ehxz-$0!eaudYb3YpZBR$su2Jl0J>+MMtk<`NrsPh zl}hzvO-)UF*REZ(TCHqpX<;-PB}s-tq3NAFcYXq30zfTEY}~l<- Date: Fri, 23 Jun 2017 16:44:07 +0200 Subject: [PATCH 15/17] [FEATURE] Mixed Wall T linked childs follow parent on edit Menu shift+a create sub menu, or add objects on menu bottom, add addon pref to choose. Floor and archipack's icons --- __init__.py | 50 +++++++++++++---- archipack_manipulator.py | 42 +++++++++------ archipack_object.py | 2 +- archipack_wall2.py | 112 ++++++++++++++++++++++++++++++++++----- 4 files changed, 169 insertions(+), 37 deletions(-) diff --git a/__init__.py b/__init__.py index ab53e05..11d88c1 100644 --- a/__init__.py +++ b/__init__.py @@ -108,7 +108,7 @@ # noinspection PyUnresolvedReferences from bpy.types import ( Panel, WindowManager, PropertyGroup, - AddonPreferences + AddonPreferences, Menu ) from bpy.props import ( EnumProperty, PointerProperty, @@ -155,7 +155,11 @@ class Archipack_Pref(AddonPreferences): default="Create", update=update_panel ) - + create_submenu = BoolProperty( + name="Use Sub-menu", + description="Put Achipack's object into a sub menu (shift+a)", + default=True + ) # Arrow sizes (world units) arrow_size = FloatProperty( name="Arrow", @@ -276,6 +280,7 @@ def draw(self, context): col.label(text="Tab Category:") col.prop(self, "tools_category") col.prop(self, "create_category") + col.prop(self, "create_submenu") box = layout.box() row = box.row() split = row.split(percentage=0.5) @@ -459,6 +464,8 @@ def poll(self, context): def draw(self, context): global icons_collection + + # is this running even when auto update not enabled ? addon_updater_ops.check_for_update_background(context) icons = icons_collection["main"] @@ -538,7 +545,7 @@ def draw(self, context): row = box.row(align=True) row.operator("archipack.floor_preset_menu", text="Floor", - # icon_value=icons["floor"].icon_id + icon_value=icons["floor"].icon_id ).preset_operator = "archipack.floor" # ---------------------------------------------------- @@ -546,12 +553,12 @@ def draw(self, context): # ---------------------------------------------------- -def menu_func(self, context): +def draw_menu(self, context): global icons_collection icons = icons_collection["main"] layout = self.layout - layout.separator() layout.operator_context = 'INVOKE_REGION_WIN' + layout.operator("archipack.wall2", text="Wall", icon_value=icons["wall"].icon_id @@ -578,10 +585,33 @@ def menu_func(self, context): ) layout.operator("archipack.floor_preset_menu", text="Floor", - # icon_value=icons["floor"].icon_id + icon_value=icons["floor"].icon_id ) +class ARCHIPACK_create_menu(Menu): + bl_label = 'Archipack' + bl_idname = 'ARCHIPACK_create_menu' + + def draw(self, context): + layout = self.layout + draw_menu(self, context) + + +def menu_func(self, context): + layout = self.layout + layout.separator() + global icons_collection + icons = icons_collection["main"] + + # either draw sub menu or right at end of this one + if context.user_preferences.addons[__name__].preferences.create_submenu: + layout.operator_context = 'INVOKE_REGION_WIN' + layout.menu("ARCHIPACK_create_menu", icon_value=icons["archipack"].icon_id) + else: + draw_menu(self, context) + + # ---------------------------------------------------- # Datablock to store global addon variables # ---------------------------------------------------- @@ -630,12 +660,13 @@ def register(): if HAS_POLYLIB: archipack_polylib.register() - bpy.types.INFO_MT_mesh_add.append(menu_func) bpy.utils.register_class(archipack_data) WindowManager.archipack = PointerProperty(type=archipack_data) bpy.utils.register_class(Archipack_Pref) update_panel(None, bpy.context) - + bpy.utils.register_class(ARCHIPACK_create_menu) + bpy.types.INFO_MT_mesh_add.append(menu_func) + addon_updater_ops.register(bl_info) # bpy.utils.register_module(__name__) @@ -643,7 +674,8 @@ def register(): def unregister(): global icons_collection bpy.types.INFO_MT_mesh_add.remove(menu_func) - + bpy.utils.unregister_class(ARCHIPACK_create_menu) + bpy.utils.unregister_class(TOOLS_PT_Archipack_PolyLib) bpy.utils.unregister_class(TOOLS_PT_Archipack_Tools) bpy.utils.unregister_class(TOOLS_PT_Archipack_Create) diff --git a/archipack_manipulator.py b/archipack_manipulator.py index 6ba7541..957cad2 100644 --- a/archipack_manipulator.py +++ b/archipack_manipulator.py @@ -58,8 +58,8 @@ manip_stack = {} -@persistent -def empty_stack(dummy=None): + +def empty_stack(): """ Empty manipulators stack on file load """ @@ -621,7 +621,7 @@ def sp_callback(self, context, event, state, sp): rM = self.o.matrix_world.inverted().to_3x3() delta = (rM * sp.delta).to_2d() x_axis = (rM * Vector((1, 0, 0))).to_2d() - + # update generator idx = 0 for p0, p1, selected in gl_pts3d: @@ -634,12 +634,12 @@ def sp_callback(self, context, event, state, sp): # move last point of segment before current if idx > 0: g.segs[idx - 1].p1 = pt - + # move first point of current segment g.segs[idx].p0 = pt idx += 1 - + # update properties from generator idx = 0 for p0, p1, selected in gl_pts3d: @@ -654,8 +654,8 @@ def sp_callback(self, context, event, state, sp): if idx > 1: part.a0 = w.delta_angle(g.segs[idx - 2]) else: - part.a0 = w.straight(1, 0).angle - + part.a0 = w.straight(1, 0).angle + if "C_" in part.type: part.radius = w.r else: @@ -664,23 +664,23 @@ def sp_callback(self, context, event, state, sp): # adjust current segment w = g.segs[idx] part = d.parts[idx] - + if idx > 0: part.a0 = w.delta_angle(g.segs[idx - 1]) else: - part.a0 = w.straight(1, 0).angle + part.a0 = w.straight(1, 0).angle # move object when point 0 self.o.location += sp.delta - + if "C_" in part.type: part.radius = w.r else: part.length = w.length - + # adjust next one if idx + 1 < d.n_parts: d.parts[idx + 1].a0 = g.segs[idx + 1].delta_angle(w) - + idx += 1 self.mouse_release(context, event) @@ -2120,7 +2120,11 @@ def manipulable_modal(self, context, event): self.manipulable_refresh = False self.manipulable_setup(context) self.manipulate_mode = True - + + if context.area is None: + self.manipulable_disable(context) + return {'FINISHED'} + context.area.tag_redraw() if self.keymap is None: @@ -2263,6 +2267,14 @@ def manipulable_manipulate(self, context, event, manipulator): return +@persistent +def cleanup(dummy=None): + global ArchipackStore + if ArchipackStore.manipulable is not None and ArchipackStore.manipulable.manipulate_mode: + ArchipackStore.manipulable.manipulable_disable(bpy.context) + empty_stack() + + def register(): # Register default manipulators global manip_stack @@ -2287,7 +2299,7 @@ def register(): bpy.utils.register_class(ARCHIPACK_OT_manipulate) bpy.utils.register_class(ARCHIPACK_OT_disable_manipulate) bpy.utils.register_class(archipack_manipulator) - bpy.app.handlers.load_pre.append(empty_stack) + bpy.app.handlers.load_pre.append(cleanup) def unregister(): @@ -2298,4 +2310,4 @@ def unregister(): bpy.utils.unregister_class(ARCHIPACK_OT_manipulate) bpy.utils.unregister_class(ARCHIPACK_OT_disable_manipulate) bpy.utils.unregister_class(archipack_manipulator) - bpy.app.handlers.load_pre.remove(empty_stack) + bpy.app.handlers.load_pre.remove(cleanup) diff --git a/archipack_object.py b/archipack_object.py index 0d371e0..3c3d160 100644 --- a/archipack_object.py +++ b/archipack_object.py @@ -171,7 +171,7 @@ def manipulate(self): print("Archipack bpy.ops.archipack.%s_manipulate not found" % (self.archipack_category)) pass - + """ d = archipack_window.datablock(o) archipack_window.filter(o) diff --git a/archipack_wall2.py b/archipack_wall2.py index 8cfeb22..2e97437 100644 --- a/archipack_wall2.py +++ b/archipack_wall2.py @@ -324,6 +324,67 @@ def update_childs(self, context): def update_manipulators(self, context): self.update(context, manipulable_refresh=True) + +def update_t_part(self, context): + """ + TODO: + set a0 as delta angle between parent wall segment and child one + rotate the wall and set a0, so y axis points inside of parent + """ + o = self.find_in_selection(context) + if o is not None: + w = context.scene.objects.get(self.t_part) + d = archipack_wall2.datablock(w) + + if d is not None: + if w.parent is not None: + + dmax = 2 * d.width + witM = w.matrix_world.inverted() + itM = witM * w.parent.matrix_world + rM = itM.to_3x3() + tM = o.matrix_world.copy() + g = d.get_generator() + og = self.get_generator() + if o.parent is None: + pt = (witM * o.location).to_2d() + dir = (witM.to_3x3() * tM.to_3x3() * og.segs[0].straight(1, 0).v.to_3d()).to_2d() + else: + pt = (itM * o.location).to_2d() + dir = (rM * tM.to_3x3() * og.segs[0].straight(1, 0).v.to_3d()).to_2d() + + for wall_idx, wall in enumerate(g.segs): + # may be optimized with a bound check + res, dist, t = wall.point_sur_segment(pt) + # outside is on the right side of the wall + # p1 + # |-- x + # p0 + if res and t > 0 and t < 1 and abs(dist) < dmax: + x = wall.straight(1, t).v + y = wall.normal(t).v.normalized() + + self.parts[0].a0 = dir.angle_signed(x) + pos = tM.translation + o.matrix_world = Matrix([ + [x.x, -y.x, 0, pos.x], + [x.y, -y.y, 0, pos.y], + [0, 0, 1, pos.z], + [0, 0, 0, 1] + ]) + break + + bpy.ops.object.select_all(action="DESELECT") + context.scene.objects.active = w.parent + o.select = True + if bpy.ops.archipack.parent_to_reference.poll(): + bpy.ops.archipack.parent_to_reference('INVOKE_DEFAULT') + + elif self.t_part != "": + self.t_part = "" + + self.restore_context(context) + def set_splits(self, value): if self.n_splits != value: @@ -556,7 +617,7 @@ def get_child(self, context): d = child.data.archipack_door[0] return child, d - + class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): parts = CollectionProperty(type=archipack_wall2_part) n_parts = IntProperty( @@ -630,7 +691,13 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): childs_manipulators = CollectionProperty(type=archipack_manipulator) # store to manipulate windows and doors childs = CollectionProperty(type=archipack_wall2_child) - + t_part = StringProperty( + name="Parent wall", + description="This part will follow parent when set", + default="", + update=update_t_part + ) + def insert_part(self, context, where): self.manipulable_disable(context) self.auto_update = False @@ -983,26 +1050,38 @@ def setup_childs(self, o, g): itM = witM * o.parent.matrix_world rM = itM.to_3x3() for child in o.parent.children: - if (child != o and - 'archipack_robusthole' not in child and - 'archipack_hole' not in child and - child.data and 'archipack_slab' not in child.data): + # filter allowed childs + cd = child.data + if (child != o and cd and ( + 'archipack_window' in cd or + 'archipack_door' in cd or ( + archipack_wall2.filter(child) and + o.name in cd.archipack_wall2[0].t_part + ) + )): + + # setup on T linked walls + if archipack_wall2.filter(child): + d = archipack_wall2.datablock(child) + cg = d.get_generator() + d.setup_childs(child, cg) + tM = child.matrix_world.to_3x3() pt = (itM * child.location).to_2d() for wall_idx, wall in enumerate(g.segs): # may be optimized with a bound check - res, d, t = wall.point_sur_segment(pt) + res, dist, t = wall.point_sur_segment(pt) # outside is on the right side of the wall # p1 # |-- x # p0 - if res and t > 0 and t < 1 and abs(d) < dmax: + if res and t > 0 and t < 1 and abs(dist) < dmax: dir = wall.normal(t).v.normalized() wall_with_childs[wall_idx] = 1 m = self.childs_manipulators.add() m.type_key = 'DUMB_SIZE' # always make window points outside - if child.data is not None and "archipack_window" in child.data: + if "archipack_window" in cd: flip = self.flip else: dir_y = (rM * tM * Vector((0, -1, 0))).to_2d() @@ -1012,7 +1091,7 @@ def setup_childs(self, o, g): relocate.append(( child.name, wall_idx, - (t * wall.length, d, (itM * child.location).z), + (t * wall.length, dist, (itM * child.location).z), flip, t)) break @@ -1070,6 +1149,13 @@ def relocate_childs(self, context, o, g): [0, 0, 1, child.pos.z], [0, 0, 0, 1] ]) + + # Update T linked wall's childs + if archipack_wall2.filter(c): + d = archipack_wall2.datablock(c) + cg = d.get_generator() + d.relocate_childs(context, c, cg) + # print("relocate_childs:%1.4f" % (time.time()-tim)) def update_childs(self, context, o, g): @@ -1346,6 +1432,8 @@ def draw(self, context): box.prop(prop, 'x_offset') row = layout.row() row.prop(prop, "closed") + row = layout.row() + row.prop_search(prop, "t_part", context.scene, "objects", text="T link", icon='OBJECT_DATAMODE') n_parts = prop.n_parts if prop.closed: n_parts += 1 @@ -1353,7 +1441,7 @@ def draw(self, context): if i < n_parts: box = layout.box() part.draw(box, context, i) - + @classmethod def poll(cls, context): return archipack_wall2.filter(context.active_object) @@ -1862,7 +1950,7 @@ def execute(self, context): self.report({'WARNING'}, "Archipack: Option only valid in Object mode") return {'CANCELLED'} - + # ------------------------------------------------------------------ # Define operator class to manipulate object # ------------------------------------------------------------------ From fd544684c13a191866eed82785eaeb1f892e895e Mon Sep 17 00:00:00 2001 From: s-leger Date: Sat, 24 Jun 2017 22:18:54 +0200 Subject: [PATCH 16/17] [BUGFIX] Crash on undo Refactory of manipulator stack to properly handle delete and reload Wall T parts link -still left a known issue : when linking to a wall without opening, rotation of child is wrong. --- archipack_manipulator.py | 182 +++++++++++------ archipack_reference_point.py | 3 +- archipack_wall2.py | 365 ++++++++++++++++++++++++----------- 3 files changed, 372 insertions(+), 178 deletions(-) diff --git a/archipack_manipulator.py b/archipack_manipulator.py index 957cad2..6e0ad79 100644 --- a/archipack_manipulator.py +++ b/archipack_manipulator.py @@ -50,40 +50,86 @@ # a global manipulator stack reference # prevent Blender "ACCESS_VIOLATION" crashes -# use a dict to prevent potential -# collisions between many objects being in -# manipulate mode (at create time) +# use a dict to prevent collisions +# between many objects being in manipulate mode # use object names as loose keys # NOTE : use app.drivers to reset before file load -manip_stack = {} +manips = {} - -def empty_stack(): +class ArchipackActiveManip: """ - Empty manipulators stack on file load + Store manipulated object """ - global manip_stack - for key in manip_stack.keys(): - for m in manip_stack[key]: + def __init__(self, object_name): + self.object_name = object_name + # manipulators stack for object + self.stack = [] + # reference to object manipulable instance + self.manipulable = None + + @property + def dirty(self): + return ( + self.manipulable is None or + len(self.stack) < 1 or + bpy.data.objects.find(self.object_name) < 0 + ) + + def exit(self): + for m in self.stack: if m is not None: m.exit() - manip_stack = {} + if self.manipulable is not None: + self.manipulable.manipulate_mode = False + self.manipulable = None + self.object_name = "" + self.stack.clear() -def in_stack(key): - global manip_stack - return key in manip_stack.keys() +def remove_manipulable(key): + global manips + # print("remove_manipulable key:%s" % (key)) + if key in manips.keys(): + manips[key].exit() + manips.pop(key) -def get_stack(key): +def check_stack(key): """ - return reference to manipulator stack for given object + check for stack item validity + use in modal to destroy invalid modals + return true when invalid / not found + false when valid """ - global manip_stack - if key not in manip_stack.keys(): - manip_stack[key] = [] - return manip_stack[key] + global manips + if key not in manips.keys(): + # print("check_stack : key not found %s" % (key)) + return True + elif manips[key].dirty: + # print("check_stack : key.dirty %s" % (key)) + remove_manipulable(key) + return True + + return False + + +def empty_stack(): + # print("empty_stack()") + global manips + for key in manips.keys(): + manips[key].exit() + manips.clear() + + +def add_manipulable(key, manipulable): + global manips + if key not in manips.keys(): + # print("add_manipulable() key:%s not found create new" % (key)) + manips[key] = ArchipackActiveManip(key) + + manips[key].manipulable = manipulable + return manips[key].stack # ------------------------------------------------------------------ @@ -549,7 +595,7 @@ def check_hover(self): def mouse_press(self, context, event): global gl_pts3d - global manip_stack + global manips if self.handle.hover: self.active = True self.handle.active = True @@ -558,7 +604,7 @@ def mouse_press(self, context, event): # get selected manipulators idx selection = [] - for m in manip_stack[self.o.name]: + for m in manips[self.o.name].stack: if m is not None and m.selected: selection.append(int(m.manipulator.prop1_name)) @@ -620,7 +666,7 @@ def sp_callback(self, context, event, state, sp): # rotation relative to object rM = self.o.matrix_world.inverted().to_3x3() delta = (rM * sp.delta).to_2d() - x_axis = (rM * Vector((1, 0, 0))).to_2d() + # x_axis = (rM * Vector((1, 0, 0))).to_2d() # update generator idx = 0 @@ -1934,10 +1980,6 @@ def setup(self, context, o, datablock, snap_callback=None): # Define Manipulable to make a PropertyGroup manipulable # ------------------------------------------------------------------ -class ArchipackStore: - # current manipulated object - manipulable = None - class ARCHIPACK_OT_manipulate(Operator): bl_idname = "archipack.manipulate" @@ -1945,12 +1987,34 @@ class ARCHIPACK_OT_manipulate(Operator): bl_description = "Manipulate" bl_options = {'REGISTER', 'UNDO'} + object_name = StringProperty(default="") + @classmethod def poll(self, context): return context.active_object is not None def modal(self, context, event): - return ArchipackStore.manipulable.manipulable_modal(context, event) + global manips + # Exit on stack change + # handle multiple object stack + # use object_name property to find manupulated object in stack + # select and make object active + # and exit when not found + key = self.object_name + if check_stack(key): + remove_manipulable(key) + # print("modal exit by check_stack(%s)" % (key)) + if context.area is not None: + context.area.tag_redraw() + return {'FINISHED'} + + res = manips[key].manipulable.manipulable_modal(context, event) + if 'FINISHED' in res: + # print("modal exit by {FINISHED}") + if context.area is not None: + context.area.tag_redraw() + remove_manipulable(key) + return res def invoke(self, context, event): if context.space_data.type == 'VIEW_3D': @@ -1964,7 +2028,7 @@ def invoke(self, context, event): class ARCHIPACK_OT_disable_manipulate(Operator): bl_idname = "archipack.disable_manipulate" bl_label = "Disable Manipulate" - bl_description = "Disable any active manipulate" + bl_description = "Disable any active manipulator" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -1972,11 +2036,8 @@ def poll(self, context): return True def execute(self, context): - if ArchipackStore.manipulable is not None and ArchipackStore.manipulable.manipulate_mode: - ArchipackStore.manipulable.manipulable_disable(context) - return {'FINISHED'} - else: - return {'CANCELLED'} + empty_stack() + return {'FINISHED'} class Manipulable(): @@ -2028,28 +2089,18 @@ def setup_manipulators(self): def manipulable_draw_callback(self, _self, context): self.manipulable_area.draw(context) - def manipulable_getstack(self, context): - o = context.active_object - if o is not None: - self.manip_stack = get_stack(o.name) - def manipulable_disable(self, context): """ disable gl draw handlers """ - # prevent complex objects manipulators - # update while not manipulating to - # kill the stack - if self.manipulate_mode: - empty_stack() + o = context.active_object + if o is not None: + remove_manipulable(o.name) + self.manip_stack = add_manipulable(o.name, self) self.manipulate_mode = False self.select_mode = False - o = context.active_object - if o is not None: - self.manip_stack = get_stack(o.name) - def manipulable_setup(self, context): """ TODO: Implement the setup part as per parent object basis @@ -2062,12 +2113,15 @@ def manipulable_setup(self, context): def _manipulable_invoke(self, context): - ArchipackStore.manipulable = self + object_name = context.active_object.name + + # store a reference to self for operators + add_manipulable(object_name, self) # take care of context switching # when call from outside of 3d view if context.space_data.type == 'VIEW_3D': - bpy.ops.archipack.manipulate('INVOKE_DEFAULT') + bpy.ops.archipack.manipulate('INVOKE_DEFAULT', object_name=object_name) else: ctx = context.copy() for window in bpy.context.window_manager.windows: @@ -2080,7 +2134,7 @@ def _manipulable_invoke(self, context): ctx['region'] = region break if ctx is not None: - bpy.ops.archipack.manipulate(ctx, 'INVOKE_DEFAULT') + bpy.ops.archipack.manipulate(ctx, 'INVOKE_DEFAULT', object_name=object_name) def manipulable_invoke(self, context): """ @@ -2091,15 +2145,16 @@ def manipulable_invoke(self, context): """ # print("manipulable_invoke self.manipulate_mode:%s" % (self.manipulate_mode)) + if self.manipulate_mode: self.manipulable_disable(context) return False - else: - bpy.ops.archipack.disable_manipulate() + # else: + # bpy.ops.archipack.disable_manipulate('INVOKE_DEFAULT') - self.manip_stack = [] + # self.manip_stack = [] # kills other's manipulators - self.manipulate_mode = True + # self.manipulate_mode = True self.manipulable_setup(context) self.manipulate_mode = True @@ -2120,11 +2175,11 @@ def manipulable_modal(self, context, event): self.manipulable_refresh = False self.manipulable_setup(context) self.manipulate_mode = True - + if context.area is None: self.manipulable_disable(context) return {'FINISHED'} - + context.area.tag_redraw() if self.keymap is None: @@ -2269,18 +2324,15 @@ def manipulable_manipulate(self, context, event, manipulator): @persistent def cleanup(dummy=None): - global ArchipackStore - if ArchipackStore.manipulable is not None and ArchipackStore.manipulable.manipulate_mode: - ArchipackStore.manipulable.manipulable_disable(bpy.context) empty_stack() def register(): # Register default manipulators - global manip_stack + global manips global manipulators_class_lookup manipulators_class_lookup = {} - manip_stack = {} + manips = {} register_manipulator('SIZE', SizeManipulator) register_manipulator('SIZE_LOC', SizeLocationManipulator) register_manipulator('ANGLE', AngleManipulator) @@ -2303,10 +2355,12 @@ def register(): def unregister(): - global manip_stack + global manips global manipulators_class_lookup + empty_stack() + del manips + manipulators_class_lookup.clear() del manipulators_class_lookup - del manip_stack bpy.utils.unregister_class(ARCHIPACK_OT_manipulate) bpy.utils.unregister_class(ARCHIPACK_OT_disable_manipulate) bpy.utils.unregister_class(archipack_manipulator) diff --git a/archipack_reference_point.py b/archipack_reference_point.py index d8910bd..528aab2 100644 --- a/archipack_reference_point.py +++ b/archipack_reference_point.py @@ -100,7 +100,6 @@ def draw(self, context): else: layout.operator('archipack.move_to_2d') - class ARCHIPACK_OT_reference_point(Operator): """Add reference point""" bl_idname = "archipack.reference_point" @@ -200,7 +199,7 @@ class ARCHIPACK_OT_store_2d_reference(Operator): def poll(cls, context): return archipack_reference_point.filter(context.active_object) -def execute(self, context): + def execute(self, context): if context.mode == "OBJECT": o = context.active_object props = archipack_reference_point.datablock(o) diff --git a/archipack_wall2.py b/archipack_wall2.py index 2e97437..7df3a45 100644 --- a/archipack_wall2.py +++ b/archipack_wall2.py @@ -272,9 +272,10 @@ def make_wall(self, step_angle, flip, closed, verts, faces): else: # last segment for j in range(wall.n_step): - print("%s" % (wall.n_step)) + continue + # print("%s" % (wall.n_step)) # wall.make_wall(j, verts, faces) - + def rotate(self, idx_from, a): """ apply rotation to all following segs @@ -291,9 +292,9 @@ def rotate(self, idx_from, a): for i in range(idx_from + 1, len(self.segs)): seg = self.segs[i] seg.rotate(a) - dp = rM * (seg.p0 - p0) + dp = rM * (seg.p0 - p0) seg.translate(dp) - + def translate(self, idx_from, dp): """ apply translation to all following segs @@ -301,11 +302,11 @@ def translate(self, idx_from, dp): 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): for seg in self.segs: seg.draw(context, render=False) - + def debug(self, verts): for wall in self.segs: for i in range(33): @@ -324,67 +325,139 @@ def update_childs(self, context): def update_manipulators(self, context): self.update(context, manipulable_refresh=True) - + def update_t_part(self, context): """ - TODO: - set a0 as delta angle between parent wall segment and child one - rotate the wall and set a0, so y axis points inside of parent + Make this wall a T child of parent wall + orient child so y points inside wall and x follow wall segment + set child a0 according """ - o = self.find_in_selection(context) + o = self.find_in_selection(context) if o is not None: + + # w is parent wall w = context.scene.objects.get(self.t_part) - d = archipack_wall2.datablock(w) - - if d is not None: - if w.parent is not None: - - dmax = 2 * d.width - witM = w.matrix_world.inverted() - itM = witM * w.parent.matrix_world - rM = itM.to_3x3() - tM = o.matrix_world.copy() - g = d.get_generator() - og = self.get_generator() + wd = archipack_wall2.datablock(w) + + if wd is not None: + og = self.get_generator() + self.setup_childs(o, og) + + bpy.ops.object.select_all(action="DESELECT") + + # 5 cases here: + # 1 No parents at all + # 2 o has parent + # 3 w has parent + # 4 o and w share same parent allready + # 5 o and w dosent share parent + link_to_parent = False + + # when both walls do have a reference point, we may delete one of them + to_delete = None + + # select childs and make parent reference point active + if w.parent is None: + # Either link to o.parent or create new parent + link_to_parent = True if o.parent is None: - pt = (witM * o.location).to_2d() - dir = (witM.to_3x3() * tM.to_3x3() * og.segs[0].straight(1, 0).v.to_3d()).to_2d() + # create a reference point and make it active + x, y, z = w.bound_box[0] + context.scene.cursor_location = w.matrix_world * Vector((x, y, z)) + # fix issue #9 + context.scene.objects.active = o + bpy.ops.archipack.reference_point() + o.select = True else: - pt = (itM * o.location).to_2d() - dir = (rM * tM.to_3x3() * og.segs[0].straight(1, 0).v.to_3d()).to_2d() - - for wall_idx, wall in enumerate(g.segs): - # may be optimized with a bound check - res, dist, t = wall.point_sur_segment(pt) - # outside is on the right side of the wall - # p1 - # |-- x - # p0 - if res and t > 0 and t < 1 and abs(dist) < dmax: - x = wall.straight(1, t).v - y = wall.normal(t).v.normalized() - - self.parts[0].a0 = dir.angle_signed(x) - pos = tM.translation - o.matrix_world = Matrix([ - [x.x, -y.x, 0, pos.x], - [x.y, -y.y, 0, pos.y], - [0, 0, 1, pos.z], - [0, 0, 0, 1] - ]) - break - + context.scene.objects.active = o.parent + w.select = True + else: + # w has parent + if o.parent is not w.parent: + link_to_parent = True + context.scene.objects.active = w.parent + o.select = True + if o.parent is not None: + # store o.parent to delete it + to_delete = o.parent + for c in o.parent.children: + if c is not o: + c.hide_select = False + c.select = True + + parent = context.active_object + + dmax = 2 * wd.width + + wg = wd.get_generator() + + otM = o.matrix_world + orM = Matrix([ + otM[0].to_2d(), + otM[1].to_2d() + ]) + + wtM = w.matrix_world + wrM = Matrix([ + wtM[0].to_2d(), + wtM[1].to_2d() + ]) + + # dir in absolute world coordsys + dir = orM * og.segs[0].straight(1, 0).v + + # pt in w coordsys + pos = otM.translation + pt = (wtM.inverted() * pos).to_2d() + + for wall_idx, wall in enumerate(wg.segs): + res, dist, t = wall.point_sur_segment(pt) + # outside is on the right side of the wall + # p1 + # |-- x + # p0 + + # NOTE: + # rotation here is wrong when w has not parent while o has parent + + if res and t > 0 and t < 1 and abs(dist) < dmax: + x = wrM * wall.straight(1, t).v + y = wrM * wall.normal(t).v.normalized() + self.parts[0].a0 = dir.angle_signed(x) + o.matrix_world = Matrix([ + [x.x, -y.x, 0, pos.x], + [x.y, -y.y, 0, pos.y], + [0, 0, 1, pos.z], + [0, 0, 0, 1] + ]) + break + + if link_to_parent and bpy.ops.archipack.parent_to_reference.poll(): + bpy.ops.archipack.parent_to_reference('INVOKE_DEFAULT') + + # update generator to take new rotation in account + # use this to relocate windows on wall after reparenting + g = self.get_generator() + self.relocate_childs(context, o, g) + + # hide holes from select + for c in parent.children: + if "archipack_hybridhole" in c: + c.hide_select = True + + # delete unneeded reference point + if to_delete is not None: bpy.ops.object.select_all(action="DESELECT") - context.scene.objects.active = w.parent - o.select = True - if bpy.ops.archipack.parent_to_reference.poll(): - bpy.ops.archipack.parent_to_reference('INVOKE_DEFAULT') + to_delete.select = True + context.scene.objects.active = to_delete + if bpy.ops.object.delete.poll(): + bpy.ops.object.delete(use_global=False) elif self.t_part != "": self.t_part = "" - + self.restore_context(context) - + def set_splits(self, value): if self.n_splits != value: @@ -611,13 +684,14 @@ def get_child(self, context): d = None child = context.scene.objects.get(self.child_name) if child is not None and child.data is not None: - if 'archipack_window' in child.data: - d = child.data.archipack_window[0] - elif 'archipack_door' in child.data: - d = child.data.archipack_door[0] + cd = child.data + if 'archipack_window' in cd: + d = cd.archipack_window[0] + elif 'archipack_door' in cd: + d = cd.archipack_door[0] return child, d - + class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): parts = CollectionProperty(type=archipack_wall2_part) n_parts = IntProperty( @@ -697,7 +771,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): default="", update=update_t_part ) - + def insert_part(self, context, where): self.manipulable_disable(context) self.auto_update = False @@ -737,22 +811,22 @@ def remove_part(self, context, where): g = self.get_generator() w = g.segs[where - 1] w.p1 = g.segs[where].p1 - + if where + 1 < self.n_parts: self.parts[where + 1].a0 = g.segs[where + 1].delta_angle(w) - + part = self.parts[where - 1] - + if "C_" in part.type: part.radius = w.r else: part.length = w.length - + if where > 1: part.a0 = w.delta_angle(g.segs[where - 2]) else: part.a0 = w.straight(1, 0).angle - + self.parts.remove(where) self.n_parts -= 1 # fix snap manipulators index @@ -874,14 +948,14 @@ def from_spline(self, wM, resolution, spline): else: pts.append(wM * points[-1].co) - self.from_points(pts, spline.use_cyclic_u) - - def from_points(self, pts, closed): - if self.is_cw(pts): pts = list(reversed(pts)) self.auto_update = False + self.from_points(pts, spline.use_cyclic_u) + self.auto_update = True + + def from_points(self, pts, closed): self.n_parts = len(pts) - 1 @@ -910,10 +984,40 @@ def from_points(self, pts, closed): p0 = p1 self.closed = closed + + def reverse(self, context, o): + + g = self.get_generator() + pts = [seg.p0.to_3d() for seg in g.segs] + + if self.closed: + pts.append(pts[0]) + + pts = list(reversed(pts)) + self.auto_update = False + + # location wont change for closed walls + if not self.closed: + dp = pts[0] - pts[-1] + # pre-translate as dp is in local coordsys + o.matrix_world = o.matrix_world * Matrix([ + [1, 0, 0, dp.x], + [0, 1, 0, dp.y], + [0, 0, 1, 0], + [0, 0, 0, 1], + ]) + + self.from_points(pts, self.closed) + g = self.get_generator() + + self.setup_childs(o, g) self.auto_update = True + # flip does trigger relocate and keep childs orientation + self.flip = not self.flip + def update(self, context, manipulable_refresh=False, update_childs=False): - + o = self.find_in_selection(context, self.auto_update) if o is None: @@ -939,7 +1043,7 @@ def update(self, context, manipulable_refresh=False, update_childs=False): # print("buildmesh") bmed.buildmesh(context, o, verts, faces, matids=None, uvs=None, weld=True) - + side = 1 if self.flip: side = -1 @@ -988,7 +1092,7 @@ def update(self, context, manipulable_refresh=False, update_childs=False): self.manipulable_refresh = True self.restore_context(context) - + # manipulable children objects like windows and doors def child_partition(self, array, begin, end): pivot = begin @@ -1046,28 +1150,41 @@ def setup_childs(self, o, g): wall_with_childs = [0 for i in range(self.n_parts + 1)] relocate = [] dmax = 2 * self.width - witM = o.matrix_world.inverted() - itM = witM * o.parent.matrix_world - rM = itM.to_3x3() + + wtM = o.matrix_world + wrM = Matrix([ + wtM[0].to_2d(), + wtM[1].to_2d() + ]) + witM = wtM.inverted() + for child in o.parent.children: # filter allowed childs cd = child.data - if (child != o and cd and ( + wd = archipack_wall2.datablock(child) + if (child != o and cd is not None and ( 'archipack_window' in cd or 'archipack_door' in cd or ( - archipack_wall2.filter(child) and - o.name in cd.archipack_wall2[0].t_part - ) + wd is not None and + o.name in wd.t_part + ) )): - - # setup on T linked walls - if archipack_wall2.filter(child): - d = archipack_wall2.datablock(child) - cg = d.get_generator() - d.setup_childs(child, cg) - - tM = child.matrix_world.to_3x3() - pt = (itM * child.location).to_2d() + + # setup on T linked walls + if wd is not None: + wg = wd.get_generator() + wd.setup_childs(child, wg) + + ctM = child.matrix_world + crM = Matrix([ + ctM[0].to_2d(), + ctM[1].to_2d() + ]) + + # pt in w coordsys + pos = ctM.translation + pt = (witM * pos).to_2d() + for wall_idx, wall in enumerate(g.segs): # may be optimized with a bound check res, dist, t = wall.point_sur_segment(pt) @@ -1076,7 +1193,8 @@ def setup_childs(self, o, g): # |-- x # p0 if res and t > 0 and t < 1 and abs(dist) < dmax: - dir = wall.normal(t).v.normalized() + # dir in world coordsys + dir = wrM * wall.sized_normal(t, 1).v wall_with_childs[wall_idx] = 1 m = self.childs_manipulators.add() m.type_key = 'DUMB_SIZE' @@ -1084,14 +1202,14 @@ def setup_childs(self, o, g): if "archipack_window" in cd: flip = self.flip else: - dir_y = (rM * tM * Vector((0, -1, 0))).to_2d() + dir_y = crM * Vector((0, -1)) # let door orient where user want flip = (dir_y - dir).length > 0.5 # store z in wall space relocate.append(( child.name, wall_idx, - (t * wall.length, dist, (itM * child.location).z), + (t * wall.length, dist, (witM * pos).z), flip, t)) break @@ -1122,8 +1240,8 @@ def relocate_childs(self, context, o, g): if c is None: continue t = child.pos.x / g.segs[child.wall_idx].length - n = g.segs[child.wall_idx].normal(t) - rx, ry = -n.v.normalized() + n = g.segs[child.wall_idx].sized_normal(t, 1) + rx, ry = -n.v rx, ry = ry, -rx if child.flip: rx, ry = -rx, -ry @@ -1137,9 +1255,9 @@ def relocate_childs(self, context, o, g): d.y = self.width d.auto_update = True c.select = False - x, y = n.p - (0.5 * w * n.v.normalized()) + x, y = n.p - (0.5 * w * n.v) else: - x, y = n.p - (child.pos.y * n.v.normalized()) + x, y = n.p - (child.pos.y * n.v) context.scene.objects.active = o # preTranslate @@ -1149,13 +1267,13 @@ def relocate_childs(self, context, o, g): [0, 0, 1, child.pos.z], [0, 0, 0, 1] ]) - + # Update T linked wall's childs if archipack_wall2.filter(c): d = archipack_wall2.datablock(c) cg = d.get_generator() d.relocate_childs(context, c, cg) - + # print("relocate_childs:%1.4f" % (time.time()-tim)) def update_childs(self, context, o, g): @@ -1172,7 +1290,7 @@ def update_childs(self, context, o, g): if self.flip: manip_side = -1 - itM = o.matrix_world.inverted() * o.parent.matrix_world + itM = o.matrix_world.inverted() m_idx = 0 for wall_idx, wall in enumerate(g.segs): p0 = wall.lerp(0) @@ -1184,7 +1302,7 @@ def update_childs(self, context, o, g): # child is either a window or a door wall_has_childs = True dt = 0.5 * d.x / wall.length - pt = (itM * c.location).to_2d() + pt = (itM * c.matrix_world.translation).to_2d() res, y, t = wall.point_sur_segment(pt) child.pos = (wall.length * t, y, child.pos.z) p1 = wall.lerp(t - dt) @@ -1333,10 +1451,9 @@ def manipulable_invoke(self, context): # print("manipulable_invoke") if self.manipulate_mode: self.manipulable_disable(context) - self.manipulate_mode = False return False - self.manip_stack = [] + # self.manip_stack = [] o = context.active_object g = self.get_generator() # setup childs manipulators @@ -1345,7 +1462,7 @@ def manipulable_invoke(self, context): self.update_childs(context, o, g) # dont do anything .. # self.manipulable_release(context) - self.manipulate_mode = True + # self.manipulate_mode = True self.manipulable_setup(context) self.manipulate_mode = True @@ -1356,7 +1473,7 @@ def manipulable_invoke(self, context): # Update throttle (smell hack here) # use 2 globals to store a timer and state of update_action -# NO MORE USING THIS PART, kept as it as it may be usefull in some cases +# NO MORE USING THIS PART, kept as it as it may be usefull in some cases update_timer = None update_timer_updating = False @@ -1434,6 +1551,8 @@ def draw(self, context): row.prop(prop, "closed") row = layout.row() row.prop_search(prop, "t_part", context.scene, "objects", text="T link", icon='OBJECT_DATAMODE') + row = layout.row() + row.operator("archipack.wall2_reverse", icon='FILE_REFRESH') n_parts = prop.n_parts if prop.closed: n_parts += 1 @@ -1441,7 +1560,7 @@ def draw(self, context): if i < n_parts: box = layout.box() part.draw(box, context, i) - + @classmethod def poll(cls, context): return archipack_wall2.filter(context.active_object) @@ -1635,7 +1754,7 @@ class ARCHIPACK_OT_wall2_draw(Operator): takeloc = Vector((0, 0, 0)) sel = [] act = None - + def mouse_to_plane(self, context, event): """ convert mouse pos to 3d point over plane defined by origin and normal @@ -1814,7 +1933,7 @@ def modal(self, context, event): g = d.get_generator() p0 = g.segs[-2].p0 p1 = g.segs[-2].p1 - dp = p1-p0 + dp = p1 - p0 takemat = o.matrix_world * Matrix([ [dp.x, dp.y, 0, p1.x], [dp.y, -dp.x, 0, p1.y], @@ -1826,7 +1945,7 @@ def modal(self, context, event): else: takeloc = self.mouse_to_plane(context, event) takemat = None - + snap_point(takeloc=takeloc, takemat=takemat, draw=self.sp_draw, @@ -1861,7 +1980,7 @@ def modal(self, context, event): else: self.o.select = True context.scene.objects.active = self.o - self.ensure_ccw() + # self.ensure_ccw() self.o.select = True context.scene.objects.active = self.o if bpy.ops.archipack.wall2_manipulate.poll(): @@ -1889,11 +2008,11 @@ def invoke(self, context, event): ]) self.feedback.enable() args = (self, context) - + self.sel = [o for o in context.selected_objects] self.act = context.active_object bpy.ops.object.select_all(action="DESELECT") - + self.state = 'STARTING' self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback, args, 'WINDOW', 'POST_PIXEL') @@ -1950,7 +2069,27 @@ def execute(self, context): self.report({'WARNING'}, "Archipack: Option only valid in Object mode") return {'CANCELLED'} - + +class ARCHIPACK_OT_wall2_reverse(Operator): + bl_idname = "archipack.wall2_reverse" + bl_label = "Reverse" + bl_description = "Reverse parts order" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + if context.mode == "OBJECT": + o = context.active_object + d = archipack_wall2.datablock(o) + if d is None: + return {'CANCELLED'} + d.reverse(context, o) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + # ------------------------------------------------------------------ # Define operator class to manipulate object # ------------------------------------------------------------------ @@ -1997,6 +2136,7 @@ def register(): bpy.utils.register_class(ARCHIPACK_OT_wall2_draw) bpy.utils.register_class(ARCHIPACK_OT_wall2_insert) bpy.utils.register_class(ARCHIPACK_OT_wall2_remove) + bpy.utils.register_class(ARCHIPACK_OT_wall2_reverse) bpy.utils.register_class(ARCHIPACK_OT_wall2_manipulate) bpy.utils.register_class(ARCHIPACK_OT_wall2_from_curve) bpy.utils.register_class(ARCHIPACK_OT_wall2_from_slab) @@ -2013,6 +2153,7 @@ def unregister(): bpy.utils.unregister_class(ARCHIPACK_OT_wall2_draw) bpy.utils.unregister_class(ARCHIPACK_OT_wall2_insert) bpy.utils.unregister_class(ARCHIPACK_OT_wall2_remove) + bpy.utils.unregister_class(ARCHIPACK_OT_wall2_reverse) bpy.utils.unregister_class(ARCHIPACK_OT_wall2_manipulate) bpy.utils.unregister_class(ARCHIPACK_OT_wall2_from_curve) bpy.utils.unregister_class(ARCHIPACK_OT_wall2_from_slab) From 537f3c118b17f867e7158683bf274ff075e03701 Mon Sep 17 00:00:00 2001 From: s-leger Date: Sun, 25 Jun 2017 00:34:58 +0200 Subject: [PATCH 17/17] [RELEASE] 1.2.5 --- __init__.py | 28 +++++------ addon_updater_ops.py | 2 +- archipack_2d.py | 16 ++---- archipack_door.py | 23 ++++----- archipack_fence.py | 23 +++++---- archipack_floor.py | 115 +++++++++++++++++++++---------------------- archipack_object.py | 16 +++--- archipack_polylib.py | 44 +++++++++++++++++ archipack_preset.py | 82 +++++++++++++++--------------- archipack_slab.py | 4 +- archipack_wall2.py | 14 +++--- bmesh_utils.py | 36 ++++++-------- materialutils.py | 4 +- 13 files changed, 221 insertions(+), 186 deletions(-) diff --git a/__init__.py b/__init__.py index 11d88c1..428841e 100644 --- a/__init__.py +++ b/__init__.py @@ -31,7 +31,7 @@ 'author': 's-leger', 'license': 'GPL', 'deps': 'shapely', - 'version': (1, 2, 4), + 'version': (1, 2, 5), 'blender': (2, 7, 8), 'location': 'View3D > Tools > Create > Archipack', 'warning': '', @@ -464,8 +464,7 @@ def poll(self, context): def draw(self, context): global icons_collection - - # is this running even when auto update not enabled ? + addon_updater_ops.check_for_update_background(context) icons = icons_collection["main"] @@ -534,20 +533,20 @@ def draw(self, context): ).ceiling = True addon_updater_ops.update_notice_box_ui(self, context) - + # row = box.row(align=True) # row.operator("archipack.roof", icon='CURVE_DATA') - + # toolkit # row = box.row(align=True) # row.operator("archipack.myobject") - + row = box.row(align=True) row.operator("archipack.floor_preset_menu", text="Floor", icon_value=icons["floor"].icon_id ).preset_operator = "archipack.floor" - + # ---------------------------------------------------- # ALT + A menu # ---------------------------------------------------- @@ -558,7 +557,7 @@ def draw_menu(self, context): icons = icons_collection["main"] layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' - + layout.operator("archipack.wall2", text="Wall", icon_value=icons["wall"].icon_id @@ -594,16 +593,15 @@ class ARCHIPACK_create_menu(Menu): bl_idname = 'ARCHIPACK_create_menu' def draw(self, context): - layout = self.layout draw_menu(self, context) - - + + def menu_func(self, context): layout = self.layout layout.separator() global icons_collection icons = icons_collection["main"] - + # either draw sub menu or right at end of this one if context.user_preferences.addons[__name__].preferences.create_submenu: layout.operator_context = 'INVOKE_REGION_WIN' @@ -611,7 +609,7 @@ def menu_func(self, context): else: draw_menu(self, context) - + # ---------------------------------------------------- # Datablock to store global addon variables # ---------------------------------------------------- @@ -666,7 +664,7 @@ def register(): update_panel(None, bpy.context) bpy.utils.register_class(ARCHIPACK_create_menu) bpy.types.INFO_MT_mesh_add.append(menu_func) - + addon_updater_ops.register(bl_info) # bpy.utils.register_module(__name__) @@ -675,7 +673,7 @@ def unregister(): global icons_collection bpy.types.INFO_MT_mesh_add.remove(menu_func) bpy.utils.unregister_class(ARCHIPACK_create_menu) - + bpy.utils.unregister_class(TOOLS_PT_Archipack_PolyLib) bpy.utils.unregister_class(TOOLS_PT_Archipack_Tools) bpy.utils.unregister_class(TOOLS_PT_Archipack_Create) diff --git a/addon_updater_ops.py b/addon_updater_ops.py index e085ec5..01052da 100644 --- a/addon_updater_ops.py +++ b/addon_updater_ops.py @@ -908,7 +908,7 @@ def register(bl_info): # Optional, consider turning off for production or allow as an option # This will print out additional debugging info to the console - updater.verbose = True # make False for production default + updater.verbose = False # make False for production default # Optional, customize where the addon updater processing subfolder is, # essentially a staging folder used by the updater on its own diff --git a/archipack_2d.py b/archipack_2d.py index 6862da9..609efa0 100644 --- a/archipack_2d.py +++ b/archipack_2d.py @@ -27,15 +27,15 @@ from mathutils import Vector, Matrix from math import sin, cos, pi, atan2, sqrt, acos import bpy -# allow to draw parts with gl for debug puropses +# allow to draw parts with gl for debug puropses from .archipack_gl import GlBaseLine class Projection(GlBaseLine): - + def __init__(self): GlBaseLine.__init__(self) - + def proj_xy(self, t, next=None): """ length of projection of sections at crossing line / circle intersections @@ -384,7 +384,7 @@ def make_offset(self, offset, last=None): def pts(self): return [self.p0.to_3d(), self.p1.to_3d()] - + class Circle(Projection): def __init__(self, c, radius): Projection.__init__(self) @@ -769,13 +769,7 @@ def as_curve(self, context): curve_obj = bpy.data.objects.new('ARC', curve) context.scene.objects.link(curve_obj) curve_obj.select = True - -""" -from archipack.archipack_2d import Arc -a = Arc(Vector((0, 0)), 5, 0, 1.5) -a.draw(C) -a.rotate(0.5).draw(C) -""" + class Line3d(Line): """ diff --git a/archipack_door.py b/archipack_door.py index e349b79..04f3b71 100644 --- a/archipack_door.py +++ b/archipack_door.py @@ -1593,7 +1593,7 @@ def unique(self, context): context.scene.objects.active = act for o in sel: o.select = True - + def execute(self, context): if context.mode == "OBJECT": if self.mode == 'CREATE': @@ -1667,26 +1667,26 @@ def draw_callback(self, _self, context): def add_object(self, context, event): o = context.active_object bpy.ops.object.select_all(action="DESELECT") - + if archipack_door.filter(o): - + o.select = True context.scene.objects.active = o if event.shift: bpy.ops.archipack.door(mode="UNIQUE") - + new_w = o.copy() new_w.data = o.data context.scene.objects.link(new_w) - + o = new_w o.select = True context.scene.objects.active = o - + # synch subs from parent instance bpy.ops.archipack.door(mode="REFRESH") - + else: bpy.ops.archipack.door(auto_manipulate=False, filepath=self.filepath) o = context.active_object @@ -1694,24 +1694,24 @@ def add_object(self, context, event): bpy.ops.archipack.generate_hole('INVOKE_DEFAULT') o.select = True context.scene.objects.active = o - + def modal(self, context, event): context.area.tag_redraw() o = context.active_object d = archipack_door.datablock(o) hole = None - + if d is not None: hole = d.find_hole(o) - + # hide hole from raycast if hole is not None: o.hide = True hole.hide = True res, tM, wall = self.mouse_to_matrix(context, event) - + if hole is not None: o.hide = False hole.hide = False @@ -1823,6 +1823,7 @@ def invoke(self, context, event): class ARCHIPACK_OT_door_preset_menu(PresetMenuOperator, Operator): + bl_description = "Show Doors presets" bl_idname = "archipack.door_preset_menu" bl_label = "Door Presets" preset_subdir = "archipack_door" diff --git a/archipack_fence.py b/archipack_fence.py index a2f9cfd..830f2e6 100644 --- a/archipack_fence.py +++ b/archipack_fence.py @@ -172,7 +172,7 @@ def param_t(self, angle_limit, post_spacing): 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): @@ -186,13 +186,13 @@ def param_t(self, angle_limit, post_spacing): 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 @@ -203,8 +203,7 @@ def param_t(self, angle_limit, post_spacing): 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() @@ -1274,7 +1273,7 @@ def from_spline(self, context, wM, resolution, spline): pts.append(pts[0]) else: pts.append(wM * points[-1].co) - + self.auto_update = False self.n_parts = len(pts) - 1 @@ -1297,20 +1296,24 @@ def from_spline(self, context, wM, resolution, spline): p0 = p1 self.auto_update = True - + o.matrix_world = tM * Matrix([ [1, 0, 0, pt.x], [0, 1, 0, pt.y], [0, 0, 1, pt.z], [0, 0, 0, 1] ]) - + def update_path(self, context): path = context.scene.objects.get(self.user_defined_path) if path is not None and path.type == 'CURVE': splines = path.data.splines if len(splines) > self.user_defined_spline: - self.from_spline(context, path.matrix_world, self.user_defined_resolution, splines[self.user_defined_spline]) + self.from_spline( + context, + path.matrix_world, + self.user_defined_resolution, + splines[self.user_defined_spline]) def get_generator(self): g = FenceGenerator(self.parts) diff --git a/archipack_floor.py b/archipack_floor.py index 0233800..aafad09 100644 --- a/archipack_floor.py +++ b/archipack_floor.py @@ -1,17 +1,30 @@ -# This file is part of JARCH Vis +# -*- 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. # -# JARCH Vis 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 3 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. # -# JARCH Vis 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. # -# You should have received a copy of the GNU General Public License -# along with JARCH Vis. If not, see . +# ##### END GPL LICENSE BLOCK ##### + +# + +# ---------------------------------------------------------- +# Base code inspired by JARCH Vis +# Original Author: Jacob Morris +# Author : Stephen Leger (s-leger) +# ---------------------------------------------------------- import bpy from bpy.types import Operator, PropertyGroup, Mesh, Panel @@ -544,7 +557,7 @@ def wood_regular(ow, ol, bw, bl, s_l, s_w, if is_r_h: v = z * 0.5 * (r_h / 100) - z = uniform(z / 2 - v, z / 2 + v) + z = uniform(z / 2 - v, z / 2 + v) bl2 = bl @@ -553,11 +566,11 @@ def wood_regular(ow, ol, bw, bl, s_l, s_w, bl2 = ol else: v = bl * (length_vary / 100) * 0.5 - bl2 = uniform(bl / 2 - v, bl / 2 + v) + bl2 = uniform(bl / 2 - v, bl / 2 + v) y0 = max(0, y) y1 = min(y + bl2, ol) - + if y1 > y0: p = len(verts) @@ -691,6 +704,10 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): 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, @@ -884,7 +901,13 @@ def update(self, context): auto_smooth=False) # cut hexa and herringbone wood - if self.tile_types in ('4', '23', '24'): + # 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))) @@ -900,13 +923,22 @@ def update(self, context): if self.is_grout: th = min(self.grout_depth + bevel, self.thickness - 0.001) + bottom = th else: th = self.thickness - - bmed.solidify(context, o, th) + bottom = 0 + + 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) @@ -1023,15 +1055,10 @@ def draw(self, context): 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") - # Grout - layout.separator() - layout.prop(props, "is_grout", icon="OBJECT_DATA") - if props.is_grout: - layout.prop(props, "grout_depth") - # Planks and tiles if type in (1, 21): layout.separator() @@ -1049,46 +1076,18 @@ def draw(self, context): if props.is_bevel: layout.prop(props, "bevel_res", icon="OUTLINER_DATA_CURVE") layout.prop(props, "bevel_amo") - layout.separator() + + # Grout + layout.separator() + layout.prop(props, "is_grout", icon="OBJECT_DATA") + if props.is_grout: + layout.prop(props, "grout_depth") layout.separator() layout.prop(props, "is_mat_vary", icon="MATERIAL") if props.is_mat_vary: layout.prop(props, "mat_vary") - """ - layout.prop(props, "is_unwrap", icon="GROUP_UVS") - if props.is_unwrap: - layout.prop(props, "is_random_uv", icon="RNDCURVE") - layout.separator() - - if context.scene.render.engine == "CYCLES": - layout.prop(props, "is_material", icon="MATERIAL") - else: - layout.label("Materials Only Supported With Cycles", icon="POTATO") - - if props.is_material and context.scene.render.engine == "CYCLES": - layout.separator() - layout.prop(props, "col_image", icon="COLOR") - layout.prop(props, "is_bump", icon="SMOOTHCURVE") - - if props.is_bump: - layout.prop(props, "norm_image", icon="TEXTURE") - layout.prop(props, "bump_amo") - layout.prop(props, "im_scale", icon="MAN_SCALE") - layout.prop(props, "is_rotate", icon="MAN_ROT") - - if props.flooring_types == "2": - layout.separator() - layout.prop(props, "mortar_color", icon="COLOR") - layout.prop(props, "mortar_bump") - - layout.separator() - layout.operator("mesh.flooring_materials", icon="MATERIAL") - layout.separator() - layout.prop(props, "is_preview", icon="SCENE") - """ - class ARCHIPACK_OT_floor(ArchipackCreateTool, Operator): bl_idname = "archipack.floor" diff --git a/archipack_object.py b/archipack_object.py index 3c3d160..5659a62 100644 --- a/archipack_object.py +++ b/archipack_object.py @@ -88,22 +88,22 @@ def find_in_selection(self, context, auto_update=True): """ if not auto_update: return None - + active = context.active_object selected = [o for o in context.selected_objects] - + for o in selected: if self.__class__.datablock(o) == self: self.previously_selected = selected self.previously_active = active return o - + return None - + def restore_context(self, context): # restore context bpy.ops.object.select_all(action="DESELECT") - + try: for o in self.previously_selected: o.select = True @@ -114,8 +114,8 @@ def restore_context(self, context): context.scene.objects.active = self.previously_active self.previously_selected = None self.previously_active = None - - + + class ArchipackCreateTool(): """ Shared property of archipack's create tool Operator @@ -171,7 +171,7 @@ def manipulate(self): print("Archipack bpy.ops.archipack.%s_manipulate not found" % (self.archipack_category)) pass - + """ d = archipack_window.datablock(o) archipack_window.filter(o) diff --git a/archipack_polylib.py b/archipack_polylib.py index bce3f3f..49644c6 100644 --- a/archipack_polylib.py +++ b/archipack_polylib.py @@ -1277,6 +1277,8 @@ def init(self, pick_tool, context, action): ('A', 'All'), ('I', 'Inverse'), ('F', 'Create line around selection'), + # ('W', 'Create window using selection'), + # ('D', 'Create door using selection'), ('ALT+F', 'Create best fit rectangle'), ('R', 'Retrieve selection'), ('S', 'Store selection'), @@ -1326,6 +1328,48 @@ def keyboard(self, context, event): scene.objects.active = result self.ba.none() self.complete(context) + elif event.type in {'W'}: + sel = [self.geoms[i] for i in self.ba.list] + if len(sel) > 0: + scene = context.scene + geom = ShapelyOps.union(sel) + if event.alt: + tM, w, h, l_pts, w_pts = ShapelyOps.min_bounding_rect(geom) + x0 = -w / 2.0 + y0 = -h / 2.0 + x1 = w / 2.0 + y1 = h / 2.0 + poly = shapely.geometry.LineString([(x0, y0, 0), (x1, y0, 0), (x1, y1, 0), + (x0, y1, 0), (x0, y0, 0)]) + result = Io.to_curve(scene, self.coordsys, poly, 'points') + result.matrix_world = self.coordsys.world * tM + scene.objects.active = result + else: + result = Io.to_curve(scene, self.coordsys, geom.convex_hull, 'points') + scene.objects.active = result + self.ba.none() + self.complete(context) + elif event.type in {'D'}: + sel = [self.geoms[i] for i in self.ba.list] + if len(sel) > 0: + scene = context.scene + geom = ShapelyOps.union(sel) + if event.alt: + tM, w, h, l_pts, w_pts = ShapelyOps.min_bounding_rect(geom) + x0 = -w / 2.0 + y0 = -h / 2.0 + x1 = w / 2.0 + y1 = h / 2.0 + poly = shapely.geometry.LineString([(x0, y0, 0), (x1, y0, 0), (x1, y1, 0), + (x0, y1, 0), (x0, y0, 0)]) + result = Io.to_curve(scene, self.coordsys, poly, 'points') + result.matrix_world = self.coordsys.world * tM + scene.objects.active = result + else: + result = Io.to_curve(scene, self.coordsys, geom.convex_hull, 'points') + scene.objects.active = result + self.ba.none() + self.complete(context) self._draw(context) def modal(self, context, event): diff --git a/archipack_preset.py b/archipack_preset.py index f89997e..33ae7a1 100644 --- a/archipack_preset.py +++ b/archipack_preset.py @@ -30,7 +30,7 @@ from mathutils import Vector from bpy.props import StringProperty from .archipack_gl import ( - ThumbHandle, Screen, GlRect, + ThumbHandle, Screen, GlRect, GlPolyline, GlPolygon, GlText, GlHandle ) @@ -39,12 +39,12 @@ class CruxHandle(GlHandle): def __init__(self, sensor_size, depth): GlHandle.__init__(self, sensor_size, 0, True, False) - self.branch_0 = GlPolygon((1, 1, 1 ,1), d=2) - self.branch_1 = GlPolygon((1, 1, 1 ,1), d=2) - self.branch_2 = GlPolygon((1, 1, 1 ,1), d=2) - self.branch_3 = GlPolygon((1, 1, 1 ,1), d=2) + self.branch_0 = GlPolygon((1, 1, 1, 1), d=2) + self.branch_1 = GlPolygon((1, 1, 1, 1), d=2) + self.branch_2 = GlPolygon((1, 1, 1, 1), d=2) + self.branch_3 = GlPolygon((1, 1, 1, 1), d=2) self.depth = depth - + def set_pos(self, pos_2d): self.pos_2d = pos_2d o = pos_2d @@ -63,12 +63,12 @@ def set_pos(self, pos_2d): p8 = o + Vector((-c, 0)) p9 = o + Vector((-w, s)) p10 = o + Vector((-s, w)) - p11 = o + Vector((0, c)) + p11 = o + Vector((0, c)) self.branch_0.set_pos([p11, p0, p1, p2, o]) self.branch_1.set_pos([p2, p3, p4, p5, o]) self.branch_2.set_pos([p5, p6, p7, p8, o]) self.branch_3.set_pos([p8, p9, p10, p11, o]) - + @property def pts(self): return [self.pos_2d] @@ -87,8 +87,8 @@ def draw(self, context, render=False): self.branch_1.draw(context) self.branch_2.draw(context) self.branch_3.draw(context) - - + + class SeekBox(GlText, GlHandle): """ Text input to filter items by label @@ -96,22 +96,22 @@ class SeekBox(GlText, GlHandle): - add cross to empty text - get text from keyboard """ - + def __init__(self): GlHandle.__init__(self, 0, 0, True, False, d=2) GlText.__init__(self, d=2) self.sensor_width = 250 self.pos_3d = Vector((0, 0)) self.bg = GlRect(colour=(0, 0, 0, 0.7)) - self.frame = GlPolyline((1, 1, 1 ,1), d=2) + self.frame = GlPolyline((1, 1, 1, 1), d=2) self.frame.closed = True self.cancel = CruxHandle(16, 4) self.line_pos = 0 - + @property def pts(self): return [self.pos_3d] - + def set_pos(self, context, pos_2d): x, ty = self.text_size(context) w = self.sensor_width @@ -129,8 +129,8 @@ def set_pos(self, context, pos_2d): self.bg.set_pos([p0, p2]) self.frame.set_pos([p0, p1, p2, p3]) self.cancel.set_pos(pos_2d + Vector((w + 15, 0.5 * y))) - - def keyboard_entry(self, context, event): + + def keyboard_entry(self, context, event): c = event.ascii if c: if c == ",": @@ -151,18 +151,18 @@ def keyboard_entry(self, context, event): elif event.type == 'RIGHT_ARROW': self.line_pos = (self.line_pos + 1) % (len(self.label) + 1) - + def draw(self, context): self.bg.draw(context) self.frame.draw(context) GlText.draw(self, context) self.cancel.draw(context) - + @property def sensor_center(self): return self.pos_3d - + preset_paths = bpy.utils.script_paths("presets") addons_paths = bpy.utils.script_paths("addons") @@ -173,13 +173,13 @@ def __init__(self, thumbsize, preset, image=None): self.preset = preset self.handle = ThumbHandle(thumbsize, name, image, draggable=True) self.enable = True - + def filter(self, keywords): for key in keywords: if key not in self.handle.label.label: return False return True - + def set_pos(self, context, pos): self.handle.set_pos(context, pos) @@ -199,12 +199,12 @@ def draw(self, context): class PresetMenu(): - + keyboard_type = { 'BACK_SPACE', 'DEL', 'LEFT_ARROW', 'RIGHT_ARROW' } - + def __init__(self, context, category, thumbsize=Vector((150, 100))): self.imageList = [] self.menuItems = [] @@ -224,7 +224,7 @@ def __init__(self, context, category, thumbsize=Vector((150, 100))): self.border = GlPolyline((0.7, 0.7, 0.7, 1), d=2) self.keywords = SeekBox() self.keywords.colour_normal = (1, 1, 1, 1) - + self.border.closed = True self.set_pos(context) @@ -304,16 +304,16 @@ def set_pos(self, context): x = x_min y = y_max + self.y_scroll n_rows = 0 - - self.keywords.set_pos(context, p1 + 0.5 * (p2 - p1)) + + self.keywords.set_pos(context, p1 + 0.5 * (p2 - p1)) keywords = self.keywords.label.split(" ") - + for item in self.menuItems: if y > y_max or y < y_min: item.enable = False else: item.enable = True - + # filter items by name if len(keywords) > 0 and not item.filter(keywords): item.enable = False @@ -337,12 +337,12 @@ def draw(self, context): def mouse_press(self, context, event): self.mouse_position(event) - + if self.keywords.cancel.hover: self.keywords.label = "" self.keywords.line_pos = 0 self.set_pos(context) - + for item in self.menuItems: if item.enable and item.mouse_press(): # load item preset @@ -368,12 +368,12 @@ def scroll_down(self, context, event): self.y_scroll = min(self.scroll_max, self.y_scroll + (self.thumbsize.y + self.spacing.y)) self.set_pos(context) # print("scroll_down %s" % (self.y_scroll)) - + def keyboard_entry(self, context, event): self.keywords.keyboard_entry(context, event) self.set_pos(context) - - + + class PresetMenuOperator(): preset_operator = StringProperty( @@ -438,22 +438,22 @@ def modal(self, context, event): def invoke(self, context, event): if context.area.type == 'VIEW_3D': - - # with alt pressed on invoke, will bypass menu operator and + + # with alt pressed on invoke, will bypass menu operator and # call preset_operator # allow start drawing linked copy of active object if event.alt or event.ctrl: po = self.preset_operator.split(".") op = getattr(getattr(bpy.ops, po[0]), po[1]) d = context.active_object.data - + if d is not None and self.preset_subdir in d and op.poll(): op('INVOKE_DEFAULT') else: self.report({'WARNING'}, "Active object must be a " + self.preset_subdir.split("_")[1].capitalize()) return {'CANCELLED'} return {'FINISHED'} - + self.menu = PresetMenu(context, self.preset_subdir) # the arguments we pass the the callback @@ -534,7 +534,7 @@ def post_cb(self, context): # render thumb scene = context.scene render = scene.render - + # save render parame resolution_x = render.resolution_x resolution_y = render.resolution_y @@ -547,7 +547,7 @@ def post_cb(self, context): file_format = render.image_settings.file_format color_mode = render.image_settings.color_mode color_depth = render.image_settings.color_depth - + render.resolution_x = 150 render.resolution_y = 100 render.resolution_percentage = 100 @@ -560,7 +560,7 @@ def post_cb(self, context): render.image_settings.color_mode = 'RGBA' render.image_settings.color_depth = '8' bpy.ops.render.render(animation=False, write_still=True, use_viewport=False) - + # restore render params render.resolution_x = resolution_x render.resolution_y = resolution_y @@ -573,5 +573,5 @@ def post_cb(self, context): render.image_settings.file_format = file_format render.image_settings.color_mode = color_mode render.image_settings.color_depth = color_depth - + return diff --git a/archipack_slab.py b/archipack_slab.py index a2abb91..640bdd2 100644 --- a/archipack_slab.py +++ b/archipack_slab.py @@ -685,7 +685,7 @@ def setup_childs(self, o, g): # p0 dist = abs(t) * seg.length if dist < dmax and abs(d) < dmax: - print("%s %s %s %s" % (idx, dist, d, c.name)) + # print("%s %s %s %s" % (idx, dist, d, c.name)) self.add_child(c.name, idx) # synch wall @@ -711,7 +711,7 @@ def setup_childs(self, o, g): if (g.segs[i].p0 - og.segs[ji].p0).length < 0.005: j = ji + 1 part.linked_idx = ji - print("link: %s to %s" % (i, ji)) + # print("link: %s to %s" % (i, ji)) break ji += 1 diff --git a/archipack_wall2.py b/archipack_wall2.py index 7df3a45..5dbd72d 100644 --- a/archipack_wall2.py +++ b/archipack_wall2.py @@ -352,10 +352,10 @@ def update_t_part(self, context): # 4 o and w share same parent allready # 5 o and w dosent share parent link_to_parent = False - + # when both walls do have a reference point, we may delete one of them to_delete = None - + # select childs and make parent reference point active if w.parent is None: # Either link to o.parent or create new parent @@ -416,10 +416,10 @@ def update_t_part(self, context): # p1 # |-- x # p0 - + # NOTE: # rotation here is wrong when w has not parent while o has parent - + if res and t > 0 and t < 1 and abs(dist) < dmax: x = wrM * wall.straight(1, t).v y = wrM * wall.normal(t).v.normalized() @@ -439,12 +439,12 @@ def update_t_part(self, context): # use this to relocate windows on wall after reparenting g = self.get_generator() self.relocate_childs(context, o, g) - + # hide holes from select for c in parent.children: if "archipack_hybridhole" in c: c.hide_select = True - + # delete unneeded reference point if to_delete is not None: bpy.ops.object.select_all(action="DESELECT") @@ -452,7 +452,7 @@ def update_t_part(self, context): context.scene.objects.active = to_delete if bpy.ops.object.delete.poll(): bpy.ops.object.delete(use_global=False) - + elif self.t_part != "": self.t_part = "" diff --git a/bmesh_utils.py b/bmesh_utils.py index 3ad3255..2edb681 100644 --- a/bmesh_utils.py +++ b/bmesh_utils.py @@ -108,26 +108,24 @@ def addmesh(context, o, verts, faces, matids=None, uvs=None, weld=False, clean=F bm = BmeshEdit._start(context, o) nv = len(bm.verts) nf = len(bm.faces) - + for v in verts: bm.verts.new(v) - + bm.verts.ensure_lookup_table() - + for f in faces: bm.faces.new([bm.verts[nv + i] for i in f]) - + bm.faces.ensure_lookup_table() - + if matids is not None: for i, matid in enumerate(matids): bm.faces[nf + i].material_index = matid - + if uvs is not None: layer = bm.loops.layers.uv.verify() - l_i = len(uvs) for i, face in enumerate(bm.faces[nf:]): - l_j = len(uvs[i]) for j, loop in enumerate(face.loops): loop[layer].uv = uvs[i][j] @@ -145,12 +143,6 @@ def addmesh(context, o, verts, faces, matids=None, uvs=None, weld=False, clean=F bpy.ops.mesh.delete_loose() bpy.ops.object.mode_set(mode='OBJECT') - """ -from archipack.bmesh_utils import BmeshEdit as bmed -bmed.solidify(C, C.active_object, 0.1) -bmed.bevel(C, C.active_object, 0.002) - """ - @staticmethod def bevel(context, o, offset, @@ -192,7 +184,7 @@ def bevel(context, o, bm.to_mesh(o.data) bm.free() - + @staticmethod def bissect(context, o, plane_co, @@ -202,14 +194,14 @@ def bissect(context, o, clear_outer=True, clear_inner=False ): - + bm = bmesh.new() bm.from_mesh(o.data) bm.verts.ensure_lookup_table() geom = bm.verts[:] geom.extend(bm.edges[:]) geom.extend(bm.faces[:]) - + bmesh.ops.bisect_plane(bm, geom=geom, dist=dist, @@ -222,17 +214,21 @@ def bissect(context, o, bm.to_mesh(o.data) bm.free() - + @staticmethod - def solidify(context, o, amt): + def solidify(context, o, amt, floor_bottom=False, altitude=0): bm = bmesh.new() bm.from_mesh(o.data) bm.verts.ensure_lookup_table() geom = bm.faces[:] bmesh.ops.solidify(bm, geom=geom, thickness=amt) + if floor_bottom: + for v in bm.verts: + if not v.select: + v.co.z = altitude bm.to_mesh(o.data) bm.free() - + @staticmethod def verts(context, o, verts): """ diff --git a/materialutils.py b/materialutils.py index 7cb48ac..558c3ae 100644 --- a/materialutils.py +++ b/materialutils.py @@ -105,7 +105,7 @@ def add_fence_materials(obj): obj.data.materials.append(wood_mat) obj.data.materials.append(metal_mat) obj.data.materials.append(glass_mat) - + @staticmethod def add_floor_materials(obj): con_mat = MaterialUtils.build_default_mat('Floor_grout', (0.5, 0.5, 0.5)) @@ -130,7 +130,7 @@ def add_floor_materials(obj): obj.data.materials.append(alt8_mat) obj.data.materials.append(alt9_mat) obj.data.materials.append(alt10_mat) - + @staticmethod def add_handle_materials(obj): metal_mat = MaterialUtils.build_default_mat('metal', (0.4, 0.4, 0.4))