Brecht Van Lommel
ed161459d1
Patch contributed by luzpaz. Differential Revision: https://developer.blender.org/D5800
1763 lines
56 KiB
Python
1763 lines
56 KiB
Python
# -*- coding:utf-8 -*-
|
|
|
|
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
# <pep8 compliant>
|
|
|
|
# ----------------------------------------------------------
|
|
# Author: Stephen Leger (s-leger)
|
|
#
|
|
# ----------------------------------------------------------
|
|
# noinspection PyUnresolvedReferences
|
|
import bpy
|
|
# noinspection PyUnresolvedReferences
|
|
from bpy.types import Operator, PropertyGroup, Mesh, Panel
|
|
from bpy.props import (
|
|
FloatProperty, BoolProperty, IntProperty,
|
|
StringProperty, EnumProperty,
|
|
CollectionProperty
|
|
)
|
|
import bmesh
|
|
from mathutils import Vector, Matrix
|
|
from mathutils.geometry import interpolate_bezier
|
|
from math import sin, cos, pi, atan2
|
|
from .archipack_manipulator import Manipulable, archipack_manipulator
|
|
from .archipack_object import ArchipackCreateTool, ArchipackObject
|
|
from .archipack_2d import Line, Arc
|
|
from .archipack_cutter import (
|
|
CutAblePolygon, CutAbleGenerator,
|
|
ArchipackCutter,
|
|
ArchipackCutterPart
|
|
)
|
|
|
|
|
|
class Slab():
|
|
|
|
def __init__(self):
|
|
# self.colour_inactive = (1, 1, 1, 1)
|
|
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)
|
|
|
|
def curved_slab(self, a0, da, radius):
|
|
n = self.normal(1).rotate(a0).scale(radius)
|
|
if da < 0:
|
|
n.v = -n.v
|
|
a0 = n.angle
|
|
c = n.p - n.v
|
|
return CurvedSlab(c, radius, a0, da)
|
|
|
|
|
|
class StraightSlab(Slab, Line):
|
|
|
|
def __init__(self, p, v):
|
|
Line.__init__(self, p, v)
|
|
Slab.__init__(self)
|
|
|
|
|
|
class CurvedSlab(Slab, Arc):
|
|
|
|
def __init__(self, c, radius, a0, da):
|
|
Arc.__init__(self, c, radius, a0, da)
|
|
Slab.__init__(self)
|
|
|
|
|
|
class SlabGenerator(CutAblePolygon, CutAbleGenerator):
|
|
|
|
def __init__(self, parts):
|
|
self.parts = parts
|
|
self.segs = []
|
|
self.holes = []
|
|
self.convex = True
|
|
self.xsize = 0
|
|
|
|
def add_part(self, part):
|
|
|
|
if len(self.segs) < 1:
|
|
s = None
|
|
else:
|
|
s = self.segs[-1]
|
|
# start a new slab
|
|
if s is None:
|
|
if part.type == 'S_SEG':
|
|
p = Vector((0, 0))
|
|
v = part.length * Vector((cos(part.a0), sin(part.a0)))
|
|
s = StraightSlab(p, v)
|
|
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 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 = part.type
|
|
|
|
def set_offset(self):
|
|
last = None
|
|
for i, seg in enumerate(self.segs):
|
|
seg.set_offset(self.parts[i].offset, last)
|
|
last = seg.line
|
|
|
|
def close(self, closed):
|
|
# Make last segment implicit closing one
|
|
if closed:
|
|
part = self.parts[-1]
|
|
w = self.segs[-1]
|
|
dp = self.segs[0].p0 - self.segs[-1].p0
|
|
if "C_" in part.type:
|
|
dw = (w.p1 - w.p0)
|
|
w.r = part.radius / dw.length * dp.length
|
|
# angle pt - p0 - angle p0 p1
|
|
da = atan2(dp.y, dp.x) - atan2(dw.y, dw.x)
|
|
a0 = w.a0 + da
|
|
if a0 > pi:
|
|
a0 -= 2 * pi
|
|
if a0 < -pi:
|
|
a0 += 2 * pi
|
|
w.a0 = a0
|
|
else:
|
|
w.v = dp
|
|
|
|
if len(self.segs) > 1:
|
|
w.line = w.make_offset(self.parts[-1].offset, self.segs[-2].line)
|
|
|
|
p1 = self.segs[0].line.p1
|
|
self.segs[0].line = self.segs[0].make_offset(self.parts[0].offset, w.line)
|
|
self.segs[0].line.p1 = p1
|
|
|
|
def locate_manipulators(self):
|
|
"""
|
|
setup manipulators
|
|
"""
|
|
for i, f in enumerate(self.segs):
|
|
|
|
manipulators = self.parts[i].manipulators
|
|
p0 = f.p0.to_3d()
|
|
p1 = f.p1.to_3d()
|
|
# angle from last to current segment
|
|
if i > 0:
|
|
v0 = self.segs[i - 1].straight(-1, 1).v.to_3d()
|
|
v1 = f.straight(1, 0).v.to_3d()
|
|
manipulators[0].set_pts([p0, v0, v1])
|
|
|
|
if type(f).__name__ == "StraightSlab":
|
|
# segment length
|
|
manipulators[1].type_key = 'SIZE'
|
|
manipulators[1].prop1_name = "length"
|
|
manipulators[1].set_pts([p0, p1, (1, 0, 0)])
|
|
else:
|
|
# segment radius + angle
|
|
v0 = (f.p0 - f.c).to_3d()
|
|
v1 = (f.p1 - f.c).to_3d()
|
|
manipulators[1].type_key = 'ARC_ANGLE_RADIUS'
|
|
manipulators[1].prop1_name = "da"
|
|
manipulators[1].prop2_name = "radius"
|
|
manipulators[1].set_pts([f.c.to_3d(), v0, v1])
|
|
|
|
# snap manipulator, don't change index !
|
|
manipulators[2].set_pts([p0, p1, (1, 0, 0)])
|
|
# dumb segment id
|
|
manipulators[3].set_pts([p0, p1, (1, 0, 0)])
|
|
|
|
def get_verts(self, verts):
|
|
for s in self.segs:
|
|
if "Curved" in type(s).__name__:
|
|
for i in range(16):
|
|
# x, y = slab.line.lerp(i / 16)
|
|
verts.append(s.lerp(i / 16).to_3d())
|
|
else:
|
|
# x, y = s.line.p0
|
|
verts.append(s.p0.to_3d())
|
|
"""
|
|
for i in range(33):
|
|
x, y = slab.line.lerp(i / 32)
|
|
verts.append((x, y, 0))
|
|
"""
|
|
|
|
def rotate(self, idx_from, a):
|
|
"""
|
|
apply rotation to all following segs
|
|
"""
|
|
self.segs[idx_from].rotate(a)
|
|
ca = cos(a)
|
|
sa = sin(a)
|
|
rM = Matrix([
|
|
[ca, -sa],
|
|
[sa, ca]
|
|
])
|
|
# rotation center
|
|
p0 = self.segs[idx_from].p0
|
|
for i in range(idx_from + 1, len(self.segs)):
|
|
seg = self.segs[i]
|
|
# rotate seg
|
|
seg.rotate(a)
|
|
# rotate delta from rotation center to segment start
|
|
dp = rM @ (seg.p0 - p0)
|
|
seg.translate(dp)
|
|
|
|
def translate(self, idx_from, dp):
|
|
"""
|
|
apply translation to all following segs
|
|
"""
|
|
self.segs[idx_from].p1 += dp
|
|
for i in range(idx_from + 1, len(self.segs)):
|
|
self.segs[i].translate(dp)
|
|
|
|
def draw(self, context):
|
|
"""
|
|
draw generator using gl
|
|
"""
|
|
for seg in self.segs:
|
|
seg.draw(context, render=False)
|
|
|
|
def limits(self):
|
|
x_size = [s.p0.x for s in self.segs]
|
|
self.xsize = max(x_size) - min(x_size)
|
|
|
|
def cut(self, context, o):
|
|
"""
|
|
either external or holes cuts
|
|
"""
|
|
self.limits()
|
|
|
|
self.as_lines(step_angle=0.0502)
|
|
# self.segs = [s.line for s in self.segs]
|
|
|
|
for b in o.children:
|
|
d = archipack_slab_cutter.datablock(b)
|
|
if d is not None:
|
|
g = d.ensure_direction()
|
|
g.change_coordsys(b.matrix_world, o.matrix_world)
|
|
self.slice(g)
|
|
|
|
def slab(self, context, o, d):
|
|
|
|
verts = []
|
|
self.get_verts(verts)
|
|
if len(verts) > 2:
|
|
|
|
bm = bmesh.new()
|
|
|
|
for v in verts:
|
|
bm.verts.new(v)
|
|
bm.verts.ensure_lookup_table()
|
|
for i in range(1, len(verts)):
|
|
bm.edges.new((bm.verts[i - 1], bm.verts[i]))
|
|
bm.edges.new((bm.verts[-1], bm.verts[0]))
|
|
bm.edges.ensure_lookup_table()
|
|
bmesh.ops.contextual_create(bm, geom=bm.edges)
|
|
|
|
self.cut_holes(bm, self)
|
|
|
|
bmesh.ops.dissolve_limit(bm,
|
|
angle_limit=0.01,
|
|
use_dissolve_boundaries=False,
|
|
verts=bm.verts,
|
|
edges=bm.edges,
|
|
delimit={'MATERIAL'})
|
|
|
|
bm.to_mesh(o.data)
|
|
bm.free()
|
|
# geom = bm.faces[:]
|
|
# verts = bm.verts[:]
|
|
# bmesh.ops.solidify(bm, geom=geom, thickness=d.z)
|
|
|
|
# merge with object
|
|
# bmed.bmesh_join(context, o, [bm], normal_update=True)
|
|
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
|
|
def update(self, context):
|
|
self.update(context)
|
|
|
|
|
|
def update_manipulators(self, context):
|
|
self.update(context, manipulable_refresh=True)
|
|
|
|
|
|
def update_path(self, context):
|
|
self.update_path(context)
|
|
|
|
|
|
materials_enum = (
|
|
('0', 'Ceiling', '', 0),
|
|
('1', 'White', '', 1),
|
|
('2', 'Concrete', '', 2),
|
|
('3', 'Wood', '', 3),
|
|
('4', 'Metal', '', 4),
|
|
('5', 'Glass', '', 5)
|
|
)
|
|
|
|
|
|
class archipack_slab_material(PropertyGroup):
|
|
index : EnumProperty(
|
|
items=materials_enum,
|
|
default='4',
|
|
update=update
|
|
)
|
|
|
|
def find_in_selection(self, context):
|
|
"""
|
|
find witch selected object this instance belongs to
|
|
provide support for "copy to selected"
|
|
"""
|
|
selected = context.selected_objects[:]
|
|
for o in selected:
|
|
props = archipack_slab.datablock(o)
|
|
if props:
|
|
for part in props.rail_mat:
|
|
if part == self:
|
|
return props
|
|
return None
|
|
|
|
def update(self, context):
|
|
props = self.find_in_selection(context)
|
|
if props is not None:
|
|
props.update(context)
|
|
|
|
|
|
class archipack_slab_child(PropertyGroup):
|
|
"""
|
|
Store child fences to be able to sync
|
|
"""
|
|
child_name : StringProperty()
|
|
idx : IntProperty()
|
|
|
|
def get_child(self, context):
|
|
d = None
|
|
child = context.scene.objects.get(self.child_name.strip())
|
|
if child is not None and child.data is not None:
|
|
if 'archipack_fence' in child.data:
|
|
d = child.data.archipack_fence[0]
|
|
return child, d
|
|
|
|
|
|
def update_type(self, context):
|
|
|
|
d = self.find_in_selection(context)
|
|
|
|
if d is not None and d.auto_update:
|
|
|
|
d.auto_update = False
|
|
# find part index
|
|
idx = 0
|
|
for i, part in enumerate(d.parts):
|
|
if part == self:
|
|
idx = i
|
|
break
|
|
|
|
part = d.parts[idx]
|
|
a0 = 0
|
|
if idx > 0:
|
|
g = d.get_generator()
|
|
w0 = g.segs[idx - 1]
|
|
a0 = w0.straight(1).angle
|
|
if "C_" in self.type:
|
|
w = w0.straight_slab(part.a0, part.length)
|
|
else:
|
|
w = w0.curved_slab(part.a0, part.da, part.radius)
|
|
else:
|
|
if "C_" in self.type:
|
|
p = Vector((0, 0))
|
|
v = self.length * Vector((cos(self.a0), sin(self.a0)))
|
|
w = StraightSlab(p, v)
|
|
a0 = pi / 2
|
|
else:
|
|
c = -self.radius * Vector((cos(self.a0), sin(self.a0)))
|
|
w = CurvedSlab(c, self.radius, self.a0, pi)
|
|
|
|
# w0 - w - w1
|
|
if idx + 1 == d.n_parts:
|
|
dp = - w.p0
|
|
else:
|
|
dp = w.p1 - w.p0
|
|
|
|
if "C_" in self.type:
|
|
part.radius = 0.5 * dp.length
|
|
part.da = pi
|
|
a0 = atan2(dp.y, dp.x) - pi / 2 - a0
|
|
else:
|
|
part.length = dp.length
|
|
a0 = atan2(dp.y, dp.x) - a0
|
|
|
|
if a0 > pi:
|
|
a0 -= 2 * pi
|
|
if a0 < -pi:
|
|
a0 += 2 * pi
|
|
part.a0 = a0
|
|
|
|
if idx + 1 < d.n_parts:
|
|
# adjust rotation of next part
|
|
part1 = d.parts[idx + 1]
|
|
if "C_" in part.type:
|
|
a0 = part1.a0 - pi / 2
|
|
else:
|
|
a0 = part1.a0 + w.straight(1).angle - atan2(dp.y, dp.x)
|
|
|
|
if a0 > pi:
|
|
a0 -= 2 * pi
|
|
if a0 < -pi:
|
|
a0 += 2 * pi
|
|
part1.a0 = a0
|
|
|
|
d.auto_update = True
|
|
|
|
|
|
class ArchipackSegment():
|
|
"""
|
|
A single manipulable polyline like segment
|
|
polyline like segment line or arc based
|
|
@TODO: share this base class with
|
|
stair, wall, fence, slab
|
|
"""
|
|
type : EnumProperty(
|
|
items=(
|
|
('S_SEG', 'Straight', '', 0),
|
|
('C_SEG', 'Curved', '', 1),
|
|
),
|
|
default='S_SEG',
|
|
update=update_type
|
|
)
|
|
length : FloatProperty(
|
|
name="Length",
|
|
min=0.01,
|
|
default=2.0,
|
|
update=update
|
|
)
|
|
radius : FloatProperty(
|
|
name="Radius",
|
|
min=0.5,
|
|
default=0.7,
|
|
update=update
|
|
)
|
|
da : FloatProperty(
|
|
name="Angle",
|
|
min=-pi,
|
|
max=pi,
|
|
default=pi / 2,
|
|
subtype='ANGLE', unit='ROTATION',
|
|
update=update
|
|
)
|
|
a0 : FloatProperty(
|
|
name="Start angle",
|
|
min=-2 * pi,
|
|
max=2 * pi,
|
|
default=0,
|
|
subtype='ANGLE', unit='ROTATION',
|
|
update=update
|
|
)
|
|
offset : FloatProperty(
|
|
name="Offset",
|
|
description="Add to current segment offset",
|
|
default=0,
|
|
unit='LENGTH', subtype='DISTANCE',
|
|
update=update
|
|
)
|
|
linked_idx : IntProperty(default=-1)
|
|
|
|
# @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):
|
|
raise NotImplementedError
|
|
|
|
def update(self, context, manipulable_refresh=False):
|
|
props = self.find_in_selection(context)
|
|
if props is not None:
|
|
props.update(context, manipulable_refresh)
|
|
|
|
def draw_insert(self, context, layout, index):
|
|
"""
|
|
May implement draw for insert / remove segment operators
|
|
"""
|
|
pass
|
|
|
|
def draw(self, context, layout, index):
|
|
box = layout.box()
|
|
box.prop(self, "type", text=str(index + 1))
|
|
self.draw_insert(context, box, index)
|
|
if self.type in ['C_SEG']:
|
|
box.prop(self, "radius")
|
|
box.prop(self, "da")
|
|
else:
|
|
box.prop(self, "length")
|
|
box.prop(self, "a0")
|
|
# box.prop(self, "offset")
|
|
|
|
|
|
class archipack_slab_part(ArchipackSegment, PropertyGroup):
|
|
|
|
def draw_insert(self, context, layout, index):
|
|
row = layout.row(align=True)
|
|
row.operator("archipack.slab_insert", text="Split").index = index
|
|
row.operator("archipack.slab_balcony", text="Balcony").index = index
|
|
row.operator("archipack.slab_remove", text="Remove").index = index
|
|
|
|
def find_in_selection(self, context):
|
|
"""
|
|
find witch selected object this instance belongs to
|
|
provide support for "copy to selected"
|
|
"""
|
|
selected = context.selected_objects[:]
|
|
for o in selected:
|
|
props = archipack_slab.datablock(o)
|
|
if props:
|
|
for part in props.parts:
|
|
if part == self:
|
|
return props
|
|
return None
|
|
|
|
|
|
class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
|
|
# boundary
|
|
n_parts : IntProperty(
|
|
name="Parts",
|
|
min=1,
|
|
default=1, update=update_manipulators
|
|
)
|
|
parts : CollectionProperty(type=archipack_slab_part)
|
|
closed : BoolProperty(
|
|
default=True,
|
|
name="Close",
|
|
options={'SKIP_SAVE'},
|
|
update=update_manipulators
|
|
)
|
|
# UI layout related
|
|
parts_expand : BoolProperty(
|
|
options={'SKIP_SAVE'},
|
|
default=False
|
|
)
|
|
|
|
x_offset : FloatProperty(
|
|
name="Offset",
|
|
min=-1000, max=1000,
|
|
default=0.0, precision=2, step=1,
|
|
unit='LENGTH', subtype='DISTANCE',
|
|
update=update
|
|
)
|
|
z : FloatProperty(
|
|
name="Thickness",
|
|
default=0.3, precision=2, step=1,
|
|
unit='LENGTH', subtype='DISTANCE',
|
|
update=update
|
|
)
|
|
auto_synch : BoolProperty(
|
|
name="Auto-Synch",
|
|
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 :
|
|
# .auto_update = False
|
|
# bulk changes
|
|
# .auto_update = True
|
|
auto_update : BoolProperty(
|
|
options={'SKIP_SAVE'},
|
|
default=True,
|
|
update=update_manipulators
|
|
)
|
|
|
|
def get_generator(self):
|
|
g = SlabGenerator(self.parts)
|
|
for part in self.parts:
|
|
# type, radius, da, length
|
|
g.add_part(part)
|
|
|
|
g.set_offset()
|
|
|
|
g.close(self.closed)
|
|
g.locate_manipulators()
|
|
return g
|
|
|
|
def insert_part(self, context, where):
|
|
self.manipulable_disable(context)
|
|
self.auto_update = False
|
|
# the part we do split
|
|
part_0 = self.parts[where]
|
|
part_0.length /= 2
|
|
part_0.da /= 2
|
|
self.parts.add()
|
|
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
|
|
self.parts.move(len(self.parts) - 1, where + 1)
|
|
self.n_parts += 1
|
|
for c in self.childs:
|
|
if c.idx > where:
|
|
c.idx += 1
|
|
self.setup_manipulators()
|
|
self.auto_update = True
|
|
|
|
def insert_balcony(self, context, where):
|
|
self.manipulable_disable(context)
|
|
self.auto_update = False
|
|
|
|
# the part we do split
|
|
part_0 = self.parts[where]
|
|
part_0.length /= 3
|
|
part_0.da /= 3
|
|
|
|
# 1st part 90deg
|
|
self.parts.add()
|
|
part_1 = self.parts[len(self.parts) - 1]
|
|
part_1.type = "S_SEG"
|
|
part_1.length = 1.5
|
|
part_1.da = part_0.da
|
|
part_1.a0 = -pi / 2
|
|
# move after current one
|
|
self.parts.move(len(self.parts) - 1, where + 1)
|
|
|
|
# 2nd part -90deg
|
|
self.parts.add()
|
|
part_1 = self.parts[len(self.parts) - 1]
|
|
part_1.type = part_0.type
|
|
part_1.length = part_0.length
|
|
part_1.radius = part_0.radius + 1.5
|
|
part_1.da = part_0.da
|
|
part_1.a0 = pi / 2
|
|
# move after current one
|
|
self.parts.move(len(self.parts) - 1, where + 2)
|
|
|
|
# 3nd part -90deg
|
|
self.parts.add()
|
|
part_1 = self.parts[len(self.parts) - 1]
|
|
part_1.type = "S_SEG"
|
|
part_1.length = 1.5
|
|
part_1.da = part_0.da
|
|
part_1.a0 = pi / 2
|
|
# move after current one
|
|
self.parts.move(len(self.parts) - 1, where + 3)
|
|
|
|
# 4nd part -90deg
|
|
self.parts.add()
|
|
part_1 = self.parts[len(self.parts) - 1]
|
|
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
|
|
self.parts.move(len(self.parts) - 1, where + 4)
|
|
|
|
self.n_parts += 4
|
|
self.setup_manipulators()
|
|
|
|
for c in self.childs:
|
|
if c.idx > where:
|
|
c.idx += 4
|
|
|
|
self.auto_update = True
|
|
g = self.get_generator()
|
|
|
|
o = context.active_object
|
|
bpy.ops.archipack.fence(auto_manipulate=False)
|
|
c = context.active_object
|
|
c.select_set(state=True)
|
|
c.data.archipack_fence[0].n_parts = 3
|
|
c.select_set(state=False)
|
|
# link to o
|
|
c.location = Vector((0, 0, 0))
|
|
c.parent = o
|
|
c.location = g.segs[where + 1].p0.to_3d()
|
|
self.add_child(c.name, where + 1)
|
|
# c.matrix_world.translation = g.segs[where].p1.to_3d()
|
|
o.select_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
self.relocate_childs(context, o, g)
|
|
|
|
def add_part(self, context, length):
|
|
self.manipulable_disable(context)
|
|
self.auto_update = False
|
|
p = self.parts.add()
|
|
p.length = length
|
|
self.n_parts += 1
|
|
self.setup_manipulators()
|
|
self.auto_update = True
|
|
return p
|
|
|
|
def add_child(self, name, idx):
|
|
c = self.childs.add()
|
|
c.child_name = name
|
|
c.idx = idx
|
|
|
|
def setup_childs(self, o, g):
|
|
"""
|
|
Store childs
|
|
call after a boolean oop
|
|
"""
|
|
# print("setup_childs")
|
|
self.childs.clear()
|
|
itM = o.matrix_world.inverted()
|
|
|
|
dmax = 0.2
|
|
for c in o.children:
|
|
if (c.data and 'archipack_fence' in c.data):
|
|
pt = (itM @ c.matrix_world.translation).to_2d()
|
|
for idx, seg in enumerate(g.segs):
|
|
# may be optimized with a bound check
|
|
res, d, t = seg.point_sur_segment(pt)
|
|
# p1
|
|
# |-- x
|
|
# p0
|
|
dist = abs(t) * seg.length
|
|
if dist < dmax and abs(d) < dmax:
|
|
# 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
|
|
|
|
# find first child wall
|
|
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")
|
|
|
|
# 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) or (next_idx == 0 and i + 1 == self.n_parts):
|
|
# shared: should move last point
|
|
# and apply to next segments
|
|
# this is overridden for common segs
|
|
# but translate non common ones
|
|
dp = seg.p1 - w.segs[idx].p1
|
|
w.translate(idx, dp)
|
|
|
|
# shared: transfer 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
|
|
last_seg = None
|
|
for i, seg in enumerate(w.segs):
|
|
|
|
d.parts[i].a0 = seg.delta_angle(last_seg)
|
|
|
|
last_seg = seg
|
|
|
|
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_set(state=True)
|
|
context.view_layer.objects.active = wall
|
|
|
|
d.auto_update = True
|
|
wall.select_set(state=False)
|
|
|
|
o.select_set(state=True)
|
|
context.view_layer.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:
|
|
continue
|
|
|
|
a = g.segs[child.idx].angle
|
|
x, y = g.segs[child.idx].p0
|
|
sa = sin(a)
|
|
ca = cos(a)
|
|
|
|
if d is not None:
|
|
c.select_set(state=True)
|
|
|
|
# auto_update need object to be active to
|
|
# setup manipulators on the right object
|
|
context.view_layer.objects.active = c
|
|
|
|
d.auto_update = False
|
|
for i, part in enumerate(d.parts):
|
|
if "C_" in self.parts[i + child.idx].type:
|
|
part.type = "C_FENCE"
|
|
else:
|
|
part.type = "S_FENCE"
|
|
part.a0 = self.parts[i + child.idx].a0
|
|
part.da = self.parts[i + child.idx].da
|
|
part.length = self.parts[i + child.idx].length
|
|
part.radius = self.parts[i + child.idx].radius
|
|
d.parts[0].a0 = pi / 2
|
|
d.auto_update = True
|
|
c.select_set(state=False)
|
|
|
|
context.view_layer.objects.active = o
|
|
# preTranslate
|
|
c.matrix_world = tM @ Matrix([
|
|
[sa, ca, 0, x],
|
|
[-ca, sa, 0, y],
|
|
[0, 0, 1, 0],
|
|
[0, 0, 0, 1]
|
|
])
|
|
|
|
def remove_part(self, context, where):
|
|
self.manipulable_disable(context)
|
|
self.auto_update = False
|
|
|
|
# preserve shape
|
|
# using generator
|
|
if where > 0:
|
|
|
|
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
|
|
|
|
for c in self.childs:
|
|
if c.idx >= where:
|
|
c.idx -= 1
|
|
self.parts.remove(where)
|
|
self.n_parts -= 1
|
|
# fix snap manipulators index
|
|
self.setup_manipulators()
|
|
self.auto_update = True
|
|
|
|
def update_parts(self, o, update_childs=False):
|
|
# print("update_parts")
|
|
# remove rows
|
|
# NOTE:
|
|
# n_parts+1
|
|
# as last one is end point of last segment or closing one
|
|
row_change = False
|
|
for i in range(len(self.parts), self.n_parts, -1):
|
|
row_change = True
|
|
self.parts.remove(i - 1)
|
|
|
|
# add rows
|
|
for i in range(len(self.parts), self.n_parts):
|
|
row_change = True
|
|
self.parts.add()
|
|
|
|
self.setup_manipulators()
|
|
|
|
g = self.get_generator()
|
|
|
|
if o is not None and (row_change or update_childs):
|
|
self.setup_childs(o, g)
|
|
|
|
return g
|
|
|
|
def setup_manipulators(self):
|
|
|
|
if len(self.manipulators) < 1:
|
|
s = self.manipulators.add()
|
|
s.type_key = "SIZE"
|
|
s.prop1_name = "z"
|
|
s.normal = Vector((0, 1, 0))
|
|
|
|
for i in range(self.n_parts):
|
|
p = self.parts[i]
|
|
n_manips = len(p.manipulators)
|
|
if n_manips < 1:
|
|
s = p.manipulators.add()
|
|
s.type_key = "ANGLE"
|
|
s.prop1_name = "a0"
|
|
p.manipulators[0].type_key = 'ANGLE'
|
|
if n_manips < 2:
|
|
s = p.manipulators.add()
|
|
s.type_key = "SIZE"
|
|
s.prop1_name = "length"
|
|
if n_manips < 3:
|
|
s = p.manipulators.add()
|
|
s.type_key = 'WALL_SNAP'
|
|
s.prop1_name = str(i)
|
|
s.prop2_name = 'z'
|
|
if n_manips < 4:
|
|
s = p.manipulators.add()
|
|
s.type_key = 'DUMB_STRING'
|
|
s.prop1_name = str(i + 1)
|
|
p.manipulators[2].prop1_name = str(i)
|
|
p.manipulators[3].prop1_name = str(i + 1)
|
|
|
|
self.parts[-1].manipulators[0].type_key = 'DUMB_ANGLE'
|
|
|
|
def is_cw(self, pts):
|
|
p0 = pts[0]
|
|
d = 0
|
|
for p in pts[1:]:
|
|
d += (p.x * p0.y - p.y * p0.x)
|
|
p0 = p
|
|
return d > 0
|
|
|
|
def interpolate_bezier(self, pts, wM, p0, p1, resolution):
|
|
# straight segment, worth testing here
|
|
# since this can lower points count by a resolution factor
|
|
# use normalized to handle non linear t
|
|
if resolution == 0:
|
|
pts.append(wM @ p0.co.to_3d())
|
|
else:
|
|
v = (p1.co - p0.co).normalized()
|
|
d1 = (p0.handle_right - p0.co).normalized()
|
|
d2 = (p1.co - p1.handle_left).normalized()
|
|
if d1 == v and d2 == v:
|
|
pts.append(wM @ p0.co.to_3d())
|
|
else:
|
|
seg = interpolate_bezier(wM @ p0.co,
|
|
wM @ p0.handle_right,
|
|
wM @ p1.handle_left,
|
|
wM @ p1.co,
|
|
resolution + 1)
|
|
for i in range(resolution):
|
|
pts.append(seg[i].to_3d())
|
|
|
|
def from_spline(self, wM, resolution, spline):
|
|
pts = []
|
|
if spline.type == 'POLY':
|
|
pts = [wM @ p.co.to_3d() for p in spline.points]
|
|
if spline.use_cyclic_u:
|
|
pts.append(pts[0])
|
|
elif spline.type == 'BEZIER':
|
|
points = spline.bezier_points
|
|
for i in range(1, len(points)):
|
|
p0 = points[i - 1]
|
|
p1 = points[i]
|
|
self.interpolate_bezier(pts, wM, p0, p1, resolution)
|
|
if spline.use_cyclic_u:
|
|
p0 = points[-1]
|
|
p1 = points[0]
|
|
self.interpolate_bezier(pts, wM, p0, p1, resolution)
|
|
pts.append(pts[0])
|
|
else:
|
|
pts.append(wM @ points[-1].co)
|
|
|
|
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)
|
|
a0 = 0
|
|
for i, p1 in enumerate(pts):
|
|
dp = p1 - p0
|
|
da = atan2(dp.y, dp.x) - a0
|
|
if da > pi:
|
|
da -= 2 * pi
|
|
if da < -pi:
|
|
da += 2 * pi
|
|
if i >= len(self.parts):
|
|
break
|
|
p = self.parts[i]
|
|
p.length = dp.to_2d().length
|
|
p.dz = dp.z
|
|
p.a0 = da
|
|
a0 += da
|
|
p0 = p1
|
|
|
|
self.closed = closed
|
|
self.auto_update = True
|
|
|
|
def make_surface(self, o, verts):
|
|
bm = bmesh.new()
|
|
for v in verts:
|
|
bm.verts.new(v)
|
|
bm.verts.ensure_lookup_table()
|
|
for i in range(1, len(verts)):
|
|
bm.edges.new((bm.verts[i - 1], bm.verts[i]))
|
|
bm.edges.new((bm.verts[-1], bm.verts[0]))
|
|
bm.edges.ensure_lookup_table()
|
|
bmesh.ops.contextual_create(bm, geom=bm.edges)
|
|
bm.to_mesh(o.data)
|
|
bm.free()
|
|
|
|
def unwrap_uv(self, o):
|
|
bm = bmesh.new()
|
|
bm.from_mesh(o.data)
|
|
for face in bm.faces:
|
|
face.select = face.material_index > 0
|
|
bm.to_mesh(o.data)
|
|
bpy.ops.uv.cube_project(scale_to_bounds=False, correct_aspect=True)
|
|
|
|
for face in bm.faces:
|
|
face.select = face.material_index < 1
|
|
bm.to_mesh(o.data)
|
|
bpy.ops.uv.smart_project(use_aspect=True, stretch_to_bounds=False)
|
|
bm.free()
|
|
|
|
def update(self, context, manipulable_refresh=False, update_childs=False):
|
|
|
|
o = self.find_in_selection(context, self.auto_update)
|
|
|
|
if o is None:
|
|
return
|
|
|
|
# clean up manipulators before any data model change
|
|
if manipulable_refresh:
|
|
self.manipulable_disable(context)
|
|
|
|
g = self.update_parts(o, update_childs)
|
|
|
|
# relocate before cutting segs
|
|
self.relocate_childs(context, o, g)
|
|
|
|
o.select_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
|
|
g.cut(context, o)
|
|
|
|
g.slab(context, o, self)
|
|
|
|
modif = o.modifiers.get('Slab')
|
|
if modif is None:
|
|
modif = o.modifiers.new('Slab', 'SOLIDIFY')
|
|
modif.use_quality_normals = True
|
|
modif.use_even_offset = True
|
|
modif.material_offset_rim = 2
|
|
modif.material_offset = 1
|
|
|
|
modif.thickness = self.z
|
|
modif.offset = 1.0
|
|
o.data.use_auto_smooth = True
|
|
bpy.ops.object.shade_smooth()
|
|
|
|
# Height
|
|
self.manipulators[0].set_pts([
|
|
(0, 0, 0),
|
|
(0, 0, -self.z),
|
|
(-1, 0, 0)
|
|
], normal=g.segs[0].straight(-1, 0).v.to_3d())
|
|
|
|
# enable manipulators rebuild
|
|
if manipulable_refresh:
|
|
self.manipulable_refresh = True
|
|
|
|
# restore context
|
|
self.restore_context(context)
|
|
|
|
def manipulable_setup(self, context):
|
|
"""
|
|
NOTE:
|
|
this one assume context.active_object is the instance this
|
|
data belongs to, failing to do so will result in wrong
|
|
manipulators set on active object
|
|
"""
|
|
self.manipulable_disable(context)
|
|
|
|
o = context.active_object
|
|
|
|
self.setup_manipulators()
|
|
|
|
for i, part in enumerate(self.parts):
|
|
if i >= self.n_parts:
|
|
break
|
|
|
|
if i > 0:
|
|
# start angle
|
|
self.manip_stack.append(part.manipulators[0].setup(context, o, part))
|
|
|
|
# length / radius + angle
|
|
self.manip_stack.append(part.manipulators[1].setup(context, o, part))
|
|
|
|
# snap point
|
|
self.manip_stack.append(part.manipulators[2].setup(context, o, self))
|
|
# index
|
|
self.manip_stack.append(part.manipulators[3].setup(context, o, self))
|
|
|
|
for m in self.manipulators:
|
|
self.manip_stack.append(m.setup(context, o, self))
|
|
|
|
def manipulable_invoke(self, context):
|
|
"""
|
|
call this in operator invoke()
|
|
"""
|
|
# print("manipulable_invoke")
|
|
if self.manipulate_mode:
|
|
self.manipulable_disable(context)
|
|
return False
|
|
|
|
o = context.active_object
|
|
g = self.get_generator()
|
|
# setup childs manipulators
|
|
self.setup_childs(o, g)
|
|
self.manipulable_setup(context)
|
|
self.manipulate_mode = True
|
|
|
|
self._manipulable_invoke(context)
|
|
|
|
return True
|
|
|
|
|
|
def update_hole(self, context):
|
|
# update parent's roof only when manipulated
|
|
self.update(context, update_parent=True)
|
|
|
|
|
|
def update_operation(self, context):
|
|
self.reverse(context, make_ccw=(self.operation == 'INTERSECTION'))
|
|
|
|
|
|
class archipack_slab_cutter_segment(ArchipackCutterPart, PropertyGroup):
|
|
manipulators : CollectionProperty(type=archipack_manipulator)
|
|
type : EnumProperty(
|
|
name="Type",
|
|
items=(
|
|
('DEFAULT', 'Side', 'Side with rake', 0),
|
|
('BOTTOM', 'Bottom', 'Bottom with gutter', 1),
|
|
('LINK', 'Side link', 'Side without decoration', 2),
|
|
('AXIS', 'Top', 'Top part with hip and beam', 3)
|
|
# ('LINK_VALLEY', 'Side valley', 'Side with valley', 3),
|
|
# ('LINK_HIP', 'Side hip', 'Side with hip', 4)
|
|
),
|
|
default='DEFAULT',
|
|
update=update_hole
|
|
)
|
|
|
|
def find_in_selection(self, context):
|
|
selected = context.selected_objects[:]
|
|
for o in selected:
|
|
d = archipack_slab_cutter.datablock(o)
|
|
if d:
|
|
for part in d.parts:
|
|
if part == self:
|
|
return d
|
|
return None
|
|
|
|
def draw(self, layout, context, index):
|
|
box = layout.box()
|
|
box.label(text="Part:" + str(index + 1))
|
|
# box.prop(self, "type", text=str(index + 1))
|
|
box.prop(self, "length")
|
|
box.prop(self, "a0")
|
|
|
|
|
|
class archipack_slab_cutter(ArchipackCutter, ArchipackObject, Manipulable, PropertyGroup):
|
|
parts : CollectionProperty(type=archipack_slab_cutter_segment)
|
|
|
|
def update_points(self, context, o, pts, update_parent=False):
|
|
self.auto_update = False
|
|
self.from_points(pts)
|
|
self.auto_update = True
|
|
if update_parent:
|
|
self.update_parent(context, o)
|
|
|
|
def update_parent(self, context, o):
|
|
|
|
d = archipack_slab.datablock(o.parent)
|
|
if d is not None:
|
|
o.parent.select_set(state=True)
|
|
context.view_layer.objects.active = o.parent
|
|
d.update(context)
|
|
o.parent.select_set(state=False)
|
|
context.view_layer.objects.active = o
|
|
|
|
|
|
class ARCHIPACK_PT_slab(Panel):
|
|
"""Archipack Slab"""
|
|
bl_idname = "ARCHIPACK_PT_slab"
|
|
bl_label = "Slab"
|
|
bl_space_type = 'VIEW_3D'
|
|
bl_region_type = 'UI'
|
|
# bl_context = 'object'
|
|
bl_category = 'Archipack'
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return archipack_slab.filter(context.active_object)
|
|
|
|
def draw(self, context):
|
|
o = context.active_object
|
|
prop = archipack_slab.datablock(o)
|
|
if prop is None:
|
|
return
|
|
layout = self.layout
|
|
layout.operator('archipack.slab_manipulate', icon='VIEW_PAN')
|
|
box = layout.box()
|
|
box.operator('archipack.slab_cutter').parent = o.name
|
|
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", text="Parts", emboss=False)
|
|
box.prop(prop, 'n_parts')
|
|
# box.prop(prop, 'closed')
|
|
for i, part in enumerate(prop.parts):
|
|
part.draw(context, layout, i)
|
|
else:
|
|
row.prop(prop, 'parts_expand', icon="TRIA_RIGHT", text="Parts", emboss=False)
|
|
|
|
|
|
class ARCHIPACK_PT_slab_cutter(Panel):
|
|
bl_idname = "ARCHIPACK_PT_slab_cutter"
|
|
bl_label = "Slab Cutter"
|
|
bl_space_type = 'VIEW_3D'
|
|
bl_region_type = 'UI'
|
|
bl_category = 'Archipack'
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return archipack_slab_cutter.filter(context.active_object)
|
|
|
|
def draw(self, context):
|
|
prop = archipack_slab_cutter.datablock(context.active_object)
|
|
if prop is None:
|
|
return
|
|
layout = self.layout
|
|
scene = context.scene
|
|
box = layout.box()
|
|
box.operator('archipack.slab_cutter_manipulate', icon='VIEW_PAN')
|
|
box.prop(prop, 'operation', text="")
|
|
box = layout.box()
|
|
box.label(text="From curve")
|
|
box.prop_search(prop, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
|
|
if prop.user_defined_path != "":
|
|
box.prop(prop, 'user_defined_resolution')
|
|
# box.prop(prop, 'x_offset')
|
|
# box.prop(prop, 'angle_limit')
|
|
"""
|
|
box.prop_search(prop, "boundary", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
|
|
"""
|
|
prop.draw(layout, context)
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Define operator class to create object
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
class ARCHIPACK_OT_slab_insert(Operator):
|
|
bl_idname = "archipack.slab_insert"
|
|
bl_label = "Insert"
|
|
bl_description = "Insert part"
|
|
bl_category = 'Archipack'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
index : IntProperty(default=0)
|
|
|
|
def execute(self, context):
|
|
if context.mode == "OBJECT":
|
|
d = archipack_slab.datablock(context.active_object)
|
|
if d is None:
|
|
return {'CANCELLED'}
|
|
d.insert_part(context, self.index)
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
class ARCHIPACK_OT_slab_balcony(Operator):
|
|
bl_idname = "archipack.slab_balcony"
|
|
bl_label = "Insert"
|
|
bl_description = "Insert part"
|
|
bl_category = 'Archipack'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
index : IntProperty(default=0)
|
|
|
|
def execute(self, context):
|
|
if context.mode == "OBJECT":
|
|
d = archipack_slab.datablock(context.active_object)
|
|
if d is None:
|
|
return {'CANCELLED'}
|
|
d.insert_balcony(context, self.index)
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
class ARCHIPACK_OT_slab_remove(Operator):
|
|
bl_idname = "archipack.slab_remove"
|
|
bl_label = "Remove"
|
|
bl_description = "Remove part"
|
|
bl_category = 'Archipack'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
index : IntProperty(default=0)
|
|
|
|
def execute(self, context):
|
|
if context.mode == "OBJECT":
|
|
d = archipack_slab.datablock(context.active_object)
|
|
if d is None:
|
|
return {'CANCELLED'}
|
|
d.remove_part(context, self.index)
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Define operator class to create object
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
class ARCHIPACK_OT_slab(ArchipackCreateTool, Operator):
|
|
bl_idname = "archipack.slab"
|
|
bl_label = "Slab"
|
|
bl_description = "Slab"
|
|
bl_category = 'Archipack'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def create(self, context):
|
|
m = bpy.data.meshes.new("Slab")
|
|
o = bpy.data.objects.new("Slab", m)
|
|
d = m.archipack_slab.add()
|
|
# make manipulators selectable
|
|
d.manipulable_selectable = True
|
|
self.link_object_to_scene(context, o)
|
|
o.select_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
self.load_preset(d)
|
|
self.add_material(o)
|
|
return o
|
|
|
|
# -----------------------------------------------------
|
|
# Execute
|
|
# -----------------------------------------------------
|
|
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_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
self.manipulate()
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
class ARCHIPACK_OT_slab_from_curve(Operator):
|
|
bl_idname = "archipack.slab_from_curve"
|
|
bl_label = "Slab curve"
|
|
bl_description = "Create a slab 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
|
|
row = layout.row()
|
|
row.label(text="Use Properties panel (N) to define parms", icon='INFO')
|
|
|
|
def create(self, context):
|
|
curve = context.active_object
|
|
bpy.ops.archipack.slab(auto_manipulate=self.auto_manipulate)
|
|
o = context.view_layer.objects.active
|
|
d = archipack_slab.datablock(o)
|
|
spline = curve.data.splines[0]
|
|
d.from_spline(curve.matrix_world, 12, spline)
|
|
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.Translation(pt)
|
|
return o
|
|
|
|
# -----------------------------------------------------
|
|
# Execute
|
|
# -----------------------------------------------------
|
|
def execute(self, context):
|
|
if context.mode == "OBJECT":
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
self.create(context)
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
class ARCHIPACK_OT_slab_from_wall(Operator):
|
|
bl_idname = "archipack.slab_from_wall"
|
|
bl_label = "->Slab"
|
|
bl_description = "Create a slab from a wall"
|
|
bl_category = 'Archipack'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
auto_manipulate : BoolProperty(default=True)
|
|
ceiling : BoolProperty(default=False)
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
o = context.active_object
|
|
return o is not None and o.data is not None and 'archipack_wall2' in o.data
|
|
|
|
def create(self, context):
|
|
wall = context.active_object
|
|
wd = wall.data.archipack_wall2[0]
|
|
bpy.ops.archipack.slab(auto_manipulate=False)
|
|
o = context.view_layer.objects.active
|
|
d = archipack_slab.datablock(o)
|
|
d.auto_update = False
|
|
d.closed = True
|
|
d.parts.clear()
|
|
d.n_parts = wd.n_parts + 1
|
|
for part in wd.parts:
|
|
p = d.parts.add()
|
|
if "S_" in part.type:
|
|
p.type = "S_SEG"
|
|
else:
|
|
p.type = "C_SEG"
|
|
p.length = part.length
|
|
p.radius = part.radius
|
|
p.da = part.da
|
|
p.a0 = part.a0
|
|
d.auto_update = True
|
|
# pretranslate
|
|
if self.ceiling:
|
|
o.matrix_world = Matrix([
|
|
[1, 0, 0, 0],
|
|
[0, 1, 0, 0],
|
|
[0, 0, 1, wd.z + d.z],
|
|
[0, 0, 0, 1],
|
|
]) @ 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.view_layer.objects.active = wall
|
|
bpy.ops.archipack.reference_point()
|
|
else:
|
|
wall.parent.select_set(state=True)
|
|
context.view_layer.objects.active = wall.parent
|
|
wall.select_set(state=True)
|
|
o.select_set(state=True)
|
|
bpy.ops.archipack.parent_to_reference()
|
|
wall.parent.select_set(state=False)
|
|
|
|
return o
|
|
|
|
# -----------------------------------------------------
|
|
# Execute
|
|
# -----------------------------------------------------
|
|
def execute(self, context):
|
|
if context.mode == "OBJECT":
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
o = self.create(context)
|
|
o.select_set(state=True)
|
|
context.view_layer.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")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
class ARCHIPACK_OT_slab_cutter(ArchipackCreateTool, Operator):
|
|
bl_idname = "archipack.slab_cutter"
|
|
bl_label = "Slab Cutter"
|
|
bl_description = "Slab Cutter"
|
|
bl_category = 'Archipack'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
parent : StringProperty("")
|
|
|
|
def create(self, context):
|
|
m = bpy.data.meshes.new("Slab Cutter")
|
|
o = bpy.data.objects.new("Slab Cutter", m)
|
|
d = m.archipack_slab_cutter.add()
|
|
parent = context.scene.objects.get(self.parent.strip())
|
|
if parent is not None:
|
|
o.parent = parent
|
|
bbox = parent.bound_box
|
|
angle_90 = pi / 2
|
|
x0, y0, z = bbox[0]
|
|
x1, y1, z = bbox[6]
|
|
x = 0.2 * (x1 - x0)
|
|
y = 0.2 * (y1 - y0)
|
|
o.matrix_world = parent.matrix_world @ Matrix([
|
|
[1, 0, 0, -3 * x],
|
|
[0, 1, 0, 0],
|
|
[0, 0, 1, 0],
|
|
[0, 0, 0, 1]
|
|
])
|
|
p = d.parts.add()
|
|
p.a0 = - angle_90
|
|
p.length = y
|
|
p = d.parts.add()
|
|
p.a0 = angle_90
|
|
p.length = x
|
|
p = d.parts.add()
|
|
p.a0 = angle_90
|
|
p.length = y
|
|
d.n_parts = 3
|
|
# d.close = True
|
|
pd = archipack_slab.datablock(parent)
|
|
pd.boundary = o.name
|
|
else:
|
|
o.location = context.scene.cursor.location
|
|
# make manipulators selectable
|
|
d.manipulable_selectable = True
|
|
self.link_object_to_scene(context, o)
|
|
o.select_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
# self.add_material(o)
|
|
self.load_preset(d)
|
|
update_operation(d, context)
|
|
return o
|
|
|
|
# -----------------------------------------------------
|
|
# Execute
|
|
# -----------------------------------------------------
|
|
def execute(self, context):
|
|
if context.mode == "OBJECT":
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
o = self.create(context)
|
|
o.select_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
self.manipulate()
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Define operator class to manipulate object
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
class ARCHIPACK_OT_slab_manipulate(Operator):
|
|
bl_idname = "archipack.slab_manipulate"
|
|
bl_label = "Manipulate"
|
|
bl_description = "Manipulate"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
return archipack_slab.filter(context.active_object)
|
|
|
|
def invoke(self, context, event):
|
|
d = archipack_slab.datablock(context.active_object)
|
|
d.manipulable_invoke(context)
|
|
return {'FINISHED'}
|
|
|
|
|
|
class ARCHIPACK_OT_slab_cutter_manipulate(Operator):
|
|
bl_idname = "archipack.slab_cutter_manipulate"
|
|
bl_label = "Manipulate"
|
|
bl_description = "Manipulate"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
return archipack_slab_cutter.filter(context.active_object)
|
|
|
|
def invoke(self, context, event):
|
|
d = archipack_slab_cutter.datablock(context.active_object)
|
|
d.manipulable_invoke(context)
|
|
return {'FINISHED'}
|
|
|
|
|
|
def register():
|
|
bpy.utils.register_class(archipack_slab_cutter_segment)
|
|
bpy.utils.register_class(archipack_slab_cutter)
|
|
Mesh.archipack_slab_cutter = CollectionProperty(type=archipack_slab_cutter)
|
|
bpy.utils.register_class(ARCHIPACK_OT_slab_cutter)
|
|
bpy.utils.register_class(ARCHIPACK_PT_slab_cutter)
|
|
bpy.utils.register_class(ARCHIPACK_OT_slab_cutter_manipulate)
|
|
|
|
bpy.utils.register_class(archipack_slab_material)
|
|
bpy.utils.register_class(archipack_slab_child)
|
|
bpy.utils.register_class(archipack_slab_part)
|
|
bpy.utils.register_class(archipack_slab)
|
|
Mesh.archipack_slab = CollectionProperty(type=archipack_slab)
|
|
bpy.utils.register_class(ARCHIPACK_PT_slab)
|
|
bpy.utils.register_class(ARCHIPACK_OT_slab)
|
|
bpy.utils.register_class(ARCHIPACK_OT_slab_insert)
|
|
bpy.utils.register_class(ARCHIPACK_OT_slab_balcony)
|
|
bpy.utils.register_class(ARCHIPACK_OT_slab_remove)
|
|
# bpy.utils.register_class(ARCHIPACK_OT_slab_manipulate_ctx)
|
|
bpy.utils.register_class(ARCHIPACK_OT_slab_manipulate)
|
|
bpy.utils.register_class(ARCHIPACK_OT_slab_from_curve)
|
|
bpy.utils.register_class(ARCHIPACK_OT_slab_from_wall)
|
|
|
|
|
|
def unregister():
|
|
bpy.utils.unregister_class(archipack_slab_material)
|
|
bpy.utils.unregister_class(archipack_slab_child)
|
|
bpy.utils.unregister_class(archipack_slab_part)
|
|
bpy.utils.unregister_class(archipack_slab)
|
|
del Mesh.archipack_slab
|
|
bpy.utils.unregister_class(ARCHIPACK_PT_slab)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_slab)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_slab_insert)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_slab_balcony)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_slab_remove)
|
|
# bpy.utils.unregister_class(ARCHIPACK_OT_slab_manipulate_ctx)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_slab_manipulate)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_slab_from_curve)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_slab_from_wall)
|
|
del Mesh.archipack_slab_cutter
|
|
bpy.utils.unregister_class(archipack_slab_cutter_segment)
|
|
bpy.utils.unregister_class(archipack_slab_cutter)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_slab_cutter)
|
|
bpy.utils.unregister_class(ARCHIPACK_PT_slab_cutter)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_slab_cutter_manipulate)
|