3 # ##### BEGIN GPL LICENSE BLOCK #####
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software Foundation,
17 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA.
19 # ##### END GPL LICENSE BLOCK #####
23 # ----------------------------------------------------------
24 # Author: Stephen Leger (s-leger)
26 # ----------------------------------------------------------
27 # noinspection PyUnresolvedReferences
29 # noinspection PyUnresolvedReferences
30 from bpy
.types
import Operator
, PropertyGroup
, Mesh
, Panel
31 from bpy
.props
import (
32 FloatProperty
, BoolProperty
, IntProperty
, CollectionProperty
,
33 StringProperty
, EnumProperty
, FloatVectorProperty
35 from .bmesh_utils
import BmeshEdit
as bmed
36 from .panel
import Panel
as Lofter
37 from mathutils
import Vector
, Matrix
38 from mathutils
.geometry
import interpolate_bezier
39 from math
import sin
, cos
, pi
, acos
, atan2
40 from .archipack_manipulator
import Manipulable
, archipack_manipulator
41 from .archipack_2d
import Line
, Arc
42 from .archipack_preset
import ArchipackPreset
, PresetMenuOperator
43 from .archipack_object
import ArchipackCreateTool
, ArchipackObject
49 # total distance from start
56 def set_offset(self
, offset
, last
=None):
58 Offset line and compute intersection point
61 self
.line
= self
.make_offset(offset
, last
)
65 return self
.t_end
- self
.t_start
67 def straight_fence(self
, a0
, length
):
68 s
= self
.straight(length
).rotate(a0
)
69 return StraightFence(s
.p
, s
.v
)
71 def curved_fence(self
, a0
, da
, radius
):
72 n
= self
.normal(1).rotate(a0
).scale(radius
)
77 return CurvedFence(c
, radius
, a0
, da
)
80 class StraightFence(Fence
, Line
):
82 return "t_start:{} t_end:{} dist:{}".format(self
.t_start
, self
.t_end
, self
.dist
)
84 def __init__(self
, p
, v
):
86 Line
.__init
__(self
, p
, v
)
89 class CurvedFence(Fence
, Arc
):
91 return "t_start:{} t_end:{} dist:{}".format(self
.t_start
, self
.t_end
, self
.dist
)
93 def __init__(self
, c
, radius
, a0
, da
):
95 Arc
.__init
__(self
, c
, radius
, a0
, da
)
100 return "t_start:{} t_end:{} n_step:{} t_step:{} i_start:{} i_end:{}".format(
101 self
.t_start
, self
.t_end
, self
.n_step
, self
.t_step
, self
.i_start
, self
.i_end
)
103 def __init__(self
, t_start
, t_end
, n_step
, t_step
, i_start
, i_end
):
104 self
.t_start
= t_start
108 self
.i_start
= i_start
112 class FenceGenerator():
114 def __init__(self
, parts
):
118 self
.user_defined_post
= None
119 self
.user_defined_uvs
= None
120 self
.user_defined_mat
= None
122 def add_part(self
, part
):
124 if len(self
.segs
) < 1:
131 if part
.type == 'S_FENCE':
133 v
= part
.length
* Vector((cos(part
.a0
), sin(part
.a0
)))
134 s
= StraightFence(p
, v
)
135 elif part
.type == 'C_FENCE':
136 c
= -part
.radius
* Vector((cos(part
.a0
), sin(part
.a0
)))
137 s
= CurvedFence(c
, part
.radius
, part
.a0
, part
.da
)
139 if part
.type == 'S_FENCE':
140 s
= s
.straight_fence(part
.a0
, part
.length
)
141 elif part
.type == 'C_FENCE':
142 s
= s
.curved_fence(part
.a0
, part
.da
, part
.radius
)
144 # s.dist = self.length
145 # self.length += s.length
147 self
.last_type
= type
149 def set_offset(self
, offset
):
151 # re-evaluate length of offset line here
153 for seg
in self
.segs
:
154 seg
.set_offset(offset
, last
)
157 def param_t(self
, angle_limit
, post_spacing
):
159 setup corners and fences dz
160 compute index of fences which belong to each group of fences between corners
161 compute t of each fence
163 # segments are group of parts separated by limit angle
170 n_parts
= len(self
.parts
) - 1
171 for i
, f
in enumerate(self
.segs
):
173 self
.length
+= f
.line
.length
177 for i
, f
in enumerate(self
.segs
):
178 dz
= self
.parts
[i
].dz
180 f
.t_start
= f
.dist
/ self
.length
184 f
.t_end
= (f
.dist
+ f
.line
.length
) / self
.length
191 vz1
= Vector((self
.segs
[i
+ 1].length
, self
.parts
[i
+ 1].dz
))
192 angle_z
= abs(vz0
.angle_signed(vz1
))
195 if (abs(self
.parts
[i
+ 1].a0
) >= angle_limit
or angle_z
>= angle_limit
):
196 l_seg
= f
.dist
+ f
.line
.length
- dist_0
197 t_seg
= f
.t_end
- t_start
198 n_fences
= max(1, int(l_seg
/ post_spacing
))
199 t_fence
= t_seg
/ n_fences
200 segment
= FenceSegment(t_start
, f
.t_end
, n_fences
, t_fence
, i_start
, i
)
201 dist_0
= f
.dist
+ f
.line
.length
204 self
.segments
.append(segment
)
206 manipulators
= self
.parts
[i
].manipulators
207 p0
= f
.line
.p0
.to_3d()
208 p1
= f
.line
.p1
.to_3d()
209 # angle from last to current segment
211 v0
= self
.segs
[i
- 1].line
.straight(-1, 1).v
.to_3d()
212 v1
= f
.line
.straight(1, 0).v
.to_3d()
213 manipulators
[0].set_pts([p0
, v0
, v1
])
215 if type(f
).__name
__ == "StraightFence":
217 manipulators
[1].type_key
= 'SIZE'
218 manipulators
[1].prop1_name
= "length"
219 manipulators
[1].set_pts([p0
, p1
, (1, 0, 0)])
221 # segment radius + angle
222 v0
= (f
.line
.p0
- f
.c
).to_3d()
223 v1
= (f
.line
.p1
- f
.c
).to_3d()
224 manipulators
[1].type_key
= 'ARC_ANGLE_RADIUS'
225 manipulators
[1].prop1_name
= "da"
226 manipulators
[1].prop2_name
= "radius"
227 manipulators
[1].set_pts([f
.c
.to_3d(), v0
, v1
])
229 # snap manipulator, don't change index !
230 manipulators
[2].set_pts([p0
, p1
, (1, 0, 0)])
233 l_seg
= f
.dist
+ f
.line
.length
- dist_0
234 t_seg
= f
.t_end
- t_start
235 n_fences
= max(1, int(l_seg
/ post_spacing
))
236 t_fence
= t_seg
/ n_fences
237 segment
= FenceSegment(t_start
, f
.t_end
, n_fences
, t_fence
, i_start
, len(self
.segs
) - 1)
238 self
.segments
.append(segment
)
240 def setup_user_defined_post(self
, o
, post_x
, post_y
, post_z
):
241 self
.user_defined_post
= o
242 x
= o
.bound_box
[6][0] - o
.bound_box
[0][0]
243 y
= o
.bound_box
[6][1] - o
.bound_box
[0][1]
244 z
= o
.bound_box
[6][2] - o
.bound_box
[0][2]
245 self
.user_defined_post_scale
= Vector((post_x
/ x
, post_y
/ -y
, post_z
/ z
))
247 # create vertex group lookup dictionary for names
248 vgroup_names
= {vgroup
.index
: vgroup
.name
for vgroup
in o
.vertex_groups
}
249 # create dictionary of vertex group assignments per vertex
250 self
.vertex_groups
= [[vgroup_names
[g
.group
] for g
in v
.groups
] for v
in m
.vertices
]
252 uv_act
= m
.uv_layers
.active
253 if uv_act
is not None:
254 uv_layer
= uv_act
.data
255 self
.user_defined_uvs
= [[uv_layer
[li
].uv
for li
in p
.loop_indices
] for p
in m
.polygons
]
257 self
.user_defined_uvs
= [[(0, 0) for i
in p
.vertices
] for p
in m
.polygons
]
259 self
.user_defined_mat
= [p
.material_index
for p
in m
.polygons
]
261 def get_user_defined_post(self
, tM
, z0
, z1
, z2
, slope
, post_z
, verts
, faces
, matids
, uvs
):
263 m
= self
.user_defined_post
.data
264 for i
, g
in enumerate(self
.vertex_groups
):
265 co
= m
.vertices
[i
].co
.copy()
266 co
.x
*= self
.user_defined_post_scale
.x
267 co
.y
*= self
.user_defined_post_scale
.y
268 co
.z
*= self
.user_defined_post_scale
.z
271 verts
.append(tM
@ co
)
272 matids
+= self
.user_defined_mat
273 faces
+= [tuple([i
+ f
for i
in p
.vertices
]) for p
in m
.polygons
]
274 uvs
+= self
.user_defined_uvs
276 def get_post(self
, post
, post_x
, post_y
, post_z
, post_alt
, sub_offset_x
,
277 id_mat
, verts
, faces
, matids
, uvs
):
282 if self
.user_defined_post
is not None:
283 x
, y
= -n
.v
.normalized()
284 p
= n
.p
+ sub_offset_x
* n
.v
.normalized()
288 [0, 0, 1, zl
+ post_alt
],
291 self
.get_user_defined_post(tM
, zl
, 0, 0, dz
, post_z
, verts
, faces
, matids
, uvs
)
294 z3
= zl
+ post_z
+ post_alt
- slope
295 z4
= zl
+ post_z
+ post_alt
+ slope
296 z0
= zl
+ post_alt
- slope
297 z1
= zl
+ post_alt
+ slope
298 vn
= n
.v
.normalized()
300 dy
= post_y
* Vector((vn
.y
, -vn
.x
))
301 oy
= sub_offset_x
* vn
302 x0
, y0
= n
.p
- dx
+ dy
+ oy
303 x1
, y1
= n
.p
- dx
- dy
+ oy
304 x2
, y2
= n
.p
+ dx
- dy
+ oy
305 x3
, y3
= n
.p
+ dx
+ dy
+ oy
307 verts
.extend([(x0
, y0
, z0
), (x0
, y0
, z3
),
308 (x1
, y1
, z1
), (x1
, y1
, z4
),
309 (x2
, y2
, z1
), (x2
, y2
, z4
),
310 (x3
, y3
, z0
), (x3
, y3
, z3
)])
311 faces
.extend([(f
, f
+ 1, f
+ 3, f
+ 2),
312 (f
+ 2, f
+ 3, f
+ 5, f
+ 4),
313 (f
+ 4, f
+ 5, f
+ 7, f
+ 6),
314 (f
+ 6, f
+ 7, f
+ 1, f
),
315 (f
, f
+ 2, f
+ 4, f
+ 6),
316 (f
+ 7, f
+ 5, f
+ 3, f
+ 1)])
317 matids
.extend([id_mat
, id_mat
, id_mat
, id_mat
, id_mat
, id_mat
])
318 x
= [(0, 0), (0, post_z
), (post_x
, post_z
), (post_x
, 0)]
319 y
= [(0, 0), (0, post_z
), (post_y
, post_z
), (post_y
, 0)]
320 z
= [(0, 0), (post_x
, 0), (post_x
, post_y
), (0, post_y
)]
321 uvs
.extend([x
, y
, x
, y
, z
, z
])
323 def get_panel(self
, subs
, altitude
, panel_x
, panel_z
, sub_offset_x
, idmat
, verts
, faces
, matids
, uvs
):
328 x0
= sub_offset_x
- 0.5 * panel_x
329 x1
= sub_offset_x
+ 0.5 * panel_x
332 profile
= [Vector((x0
, z0
)), Vector((x1
, z0
)), Vector((x1
, z1
)), Vector((x0
, z1
))]
334 n_sections
= n_subs
- 1
337 v0
= n
.v
.normalized()
338 for s
, section
in enumerate(subs
):
342 v1
= subs
[s
+ 1][0].v
.normalized()
343 dir = (v0
+ v1
).normalized()
344 scale
= 1 / cos(0.5 * acos(min(1, max(-1, v0
.dot(v1
)))))
346 x
, y
= n
.p
+ scale
* p
.x
* dir
347 z
= zl
+ p
.y
+ altitude
348 verts
.append((x
, y
, z
))
350 user_path_uv_v
.append((p1
- p0
).length
)
354 # build faces using Panel
356 # closed_shape, index, x, y, idmat
358 [i
for i
in range(len(profile
))],
359 [p
.x
for p
in profile
],
360 [p
.y
for p
in profile
],
361 [idmat
for i
in range(len(profile
))],
363 user_path_uv_v
=user_path_uv_v
,
364 user_path_verts
=n_subs
366 faces
+= lofter
.faces(16, offset
=f
, path_type
='USER_DEFINED')
367 matids
+= lofter
.mat(16, idmat
, idmat
, path_type
='USER_DEFINED')
369 uvs
+= lofter
.uv(16, v
, v
, v
, v
, 0, v
, 0, 0, path_type
='USER_DEFINED')
371 def make_subs(self
, x
, y
, z
, post_y
, altitude
,
372 sub_spacing
, offset_x
, sub_offset_x
, mat
, verts
, faces
, matids
, uvs
):
374 t_post
= (0.5 * post_y
- y
) / self
.length
375 t_spacing
= (sub_spacing
+ y
) / self
.length
377 for segment
in self
.segments
:
378 t_step
= segment
.t_step
379 t_start
= segment
.t_start
+ t_post
381 s_sub
= t_step
- 2 * t_post
382 n_sub
= int(s_sub
/ t_spacing
)
384 t_sub
= s_sub
/ n_sub
388 while s
< segment
.n_step
:
389 t_cur
= t_start
+ s
* t_step
390 for j
in range(1, n_sub
):
391 t_s
= t_cur
+ t_sub
* j
392 while self
.segs
[i
].t_end
< t_s
:
395 t
= (t_s
- f
.t_start
) / f
.t_diff
397 post
= (n
, f
.dz
/ f
.length
, f
.z0
+ f
.dz
* t
)
398 self
.get_post(post
, x
, y
, z
, altitude
, sub_offset_x
, mat
, verts
, faces
, matids
, uvs
)
401 def make_post(self
, x
, y
, z
, altitude
, x_offset
, mat
, verts
, faces
, matids
, uvs
):
403 for segment
in self
.segments
:
404 t_step
= segment
.t_step
405 t_start
= segment
.t_start
408 while s
< segment
.n_step
:
409 t_cur
= t_start
+ s
* t_step
410 while self
.segs
[i
].t_end
< t_cur
:
413 t
= (t_cur
- f
.t_start
) / f
.t_diff
415 post
= (n
, f
.dz
/ f
.line
.length
, f
.z0
+ f
.dz
* t
)
416 # self.get_post(post, x, y, z, altitude, x_offset, mat, verts, faces, matids, uvs)
417 self
.get_post(post
, x
, y
, z
, altitude
, 0, mat
, verts
, faces
, matids
, uvs
)
420 if segment
.i_end
+ 1 == len(self
.segs
):
421 f
= self
.segs
[segment
.i_end
]
423 post
= (n
, f
.dz
/ f
.line
.length
, f
.z0
+ f
.dz
)
424 # self.get_post(post, x, y, z, altitude, x_offset, mat, verts, faces, matids, uvs)
425 self
.get_post(post
, x
, y
, z
, altitude
, 0, mat
, verts
, faces
, matids
, uvs
)
427 def make_panels(self
, x
, z
, post_y
, altitude
, panel_dist
,
428 offset_x
, sub_offset_x
, idmat
, verts
, faces
, matids
, uvs
):
430 t_post
= (0.5 * post_y
+ panel_dist
) / self
.length
431 for segment
in self
.segments
:
432 t_step
= segment
.t_step
433 t_start
= segment
.t_start
436 while s
< segment
.n_step
:
438 t_cur
= t_start
+ s
* t_step
+ t_post
439 t_end
= t_start
+ (s
+ 1) * t_step
- t_post
441 while self
.segs
[i
].t_end
< t_cur
and i
< segment
.i_end
:
445 t
= (t_cur
- f
.t_start
) / f
.t_diff
447 subs
.append((n
, f
.dz
/ f
.line
.length
, f
.z0
+ f
.dz
* t
))
448 # crossing sections -> new segment
449 while i
< segment
.i_end
:
452 if type(f
).__name
__ == 'CurvedFence':
453 # can't end after segment
454 t0
= max(0, (t_cur
- f
.t_start
) / f
.t_diff
)
455 t1
= min(1, (t_end
- f
.t_start
) / f
.t_diff
)
456 n_s
= int(max(1, abs(f
.da
) * (5) / pi
- 1))
458 for j
in range(1, n_s
+ 1):
460 n
= f
.line
.sized_normal(t
, 1)
461 # n.p = f.lerp(x_offset)
462 subs
.append((n
, f
.dz
/ f
.line
.length
, f
.z0
+ f
.dz
* t
))
465 subs
.append((n
, f
.dz
/ f
.line
.length
, f
.z0
+ f
.dz
))
468 elif f
.t_start
< t_end
:
473 if type(f
).__name
__ == 'CurvedFence':
474 # can't start before segment
475 t0
= max(0, (t_cur
- f
.t_start
) / f
.t_diff
)
476 t1
= min(1, (t_end
- f
.t_start
) / f
.t_diff
)
477 n_s
= int(max(1, abs(f
.da
) * (5) / pi
- 1))
479 for j
in range(1, n_s
+ 1):
481 n
= f
.line
.sized_normal(t
, 1)
482 # n.p = f.lerp(x_offset)
483 subs
.append((n
, f
.dz
/ f
.line
.length
, f
.z0
+ f
.dz
* t
))
485 t
= (t_end
- f
.t_start
) / f
.t_diff
487 subs
.append((n
, f
.dz
/ f
.line
.length
, f
.z0
+ f
.dz
* t
))
489 # self.get_panel(subs, altitude, x, z, 0, idmat, verts, faces, matids, uvs)
490 self
.get_panel(subs
, altitude
, x
, z
, sub_offset_x
, idmat
, verts
, faces
, matids
, uvs
)
493 def make_profile(self
, profile
, idmat
,
494 x_offset
, z_offset
, extend
, verts
, faces
, matids
, uvs
):
497 for seg
in self
.segs
:
498 seg
.p_line
= seg
.make_offset(x_offset
, last
)
501 n_fences
= len(self
.segs
) - 1
511 if extend
!= 0 and f
.p_line
.length
!= 0:
512 t
= -extend
/ self
.segs
[0].p_line
.length
513 n
= f
.p_line
.sized_normal(t
, 1)
514 # n.p = f.lerp(x_offset)
515 sections
.append((n
, f
.dz
/ f
.p_line
.length
, f
.z0
+ f
.dz
* t
))
518 n
= f
.p_line
.sized_normal(0, 1)
519 # n.p = f.lerp(x_offset)
520 sections
.append((n
, f
.dz
/ f
.p_line
.length
, f
.z0
))
522 for s
, f
in enumerate(self
.segs
):
523 if f
.p_line
.length
== 0:
525 if type(f
).__name
__ == 'CurvedFence':
526 n_s
= int(max(1, abs(f
.da
) * 30 / pi
- 1))
527 for i
in range(1, n_s
+ 1):
529 n
= f
.p_line
.sized_normal(t
, 1)
530 # n.p = f.lerp(x_offset)
531 sections
.append((n
, f
.dz
/ f
.p_line
.length
, f
.z0
+ f
.dz
* t
))
533 n
= f
.p_line
.sized_normal(1, 1)
534 # n.p = f.lerp(x_offset)
535 sections
.append((n
, f
.dz
/ f
.p_line
.length
, f
.z0
+ f
.dz
))
537 if extend
!= 0 and f
.p_line
.length
!= 0:
538 t
= 1 + extend
/ self
.segs
[-1].p_line
.length
539 n
= f
.p_line
.sized_normal(t
, 1)
540 # n.p = f.lerp(x_offset)
541 sections
.append((n
, f
.dz
/ f
.p_line
.length
, f
.z0
+ f
.dz
* t
))
543 user_path_verts
= len(sections
)
545 if user_path_verts
> 0:
547 n
, dz
, z0
= sections
[-1]
548 sections
[-1] = (n
, dz
, z0
)
549 n_sections
= user_path_verts
- 1
550 n
, dz
, zl
= sections
[0]
552 v0
= n
.v
.normalized()
553 for s
, section
in enumerate(sections
):
557 v1
= sections
[s
+ 1][0].v
.normalized()
558 dir = (v0
+ v1
).normalized()
559 scale
= min(10, 1 / cos(0.5 * acos(min(1, max(-1, v0
.dot(v1
) )))))
561 # x, y = n.p + scale * (x_offset + p.x) * dir
562 x
, y
= n
.p
+ scale
* p
.x
* dir
563 z
= zl
+ p
.y
+ z_offset
564 verts
.append((x
, y
, z
))
566 user_path_uv_v
.append((p1
- p0
).length
)
570 # build faces using Panel
572 # closed_shape, index, x, y, idmat
574 [i
for i
in range(len(profile
))],
575 [p
.x
for p
in profile
],
576 [p
.y
for p
in profile
],
577 [idmat
for i
in range(len(profile
))],
579 user_path_uv_v
=user_path_uv_v
,
580 user_path_verts
=user_path_verts
582 faces
+= lofter
.faces(16, offset
=offset
, path_type
='USER_DEFINED')
583 matids
+= lofter
.mat(16, idmat
, idmat
, path_type
='USER_DEFINED')
585 uvs
+= lofter
.uv(16, v
, v
, v
, v
, 0, v
, 0, 0, path_type
='USER_DEFINED')
588 def update(self
, context
):
592 def update_manipulators(self
, context
):
593 self
.update(context
, manipulable_refresh
=True)
596 def update_path(self
, context
):
597 self
.update_path(context
)
600 def update_type(self
, context
):
602 d
= self
.find_datablock_in_selection(context
)
604 if d
is not None and d
.auto_update
:
606 d
.auto_update
= False
609 for i
, part
in enumerate(d
.parts
):
616 g
= d
.get_generator()
618 a0
= w0
.straight(1).angle
619 if "C_" in self
.type:
620 w
= w0
.straight_fence(part
.a0
, part
.length
)
622 w
= w0
.curved_fence(part
.a0
, part
.da
, part
.radius
)
624 if "C_" in self
.type:
626 v
= self
.length
* Vector((cos(self
.a0
), sin(self
.a0
)))
627 w
= StraightFence(p
, v
)
630 c
= -self
.radius
* Vector((cos(self
.a0
), sin(self
.a0
)))
631 w
= CurvedFence(c
, self
.radius
, self
.a0
, pi
)
633 # not closed, see wall
637 if "C_" in self
.type:
638 part
.radius
= 0.5 * dp
.length
640 a0
= atan2(dp
.y
, dp
.x
) - pi
/ 2 - a0
642 part
.length
= dp
.length
643 a0
= atan2(dp
.y
, dp
.x
) - a0
651 if idx
+ 1 < d
.n_parts
:
652 # adjust rotation of next part
653 part1
= d
.parts
[idx
+ 1]
654 if "C_" in part
.type:
655 a0
= part1
.a0
- pi
/ 2
657 a0
= part1
.a0
+ w
.straight(1).angle
- atan2(dp
.y
, dp
.x
)
669 ('0', 'Wood', '', 0),
670 ('1', 'Metal', '', 1),
671 ('2', 'Glass', '', 2)
675 class archipack_fence_material(PropertyGroup
):
676 index
: EnumProperty(
677 items
=materials_enum
,
682 def find_datablock_in_selection(self
, context
):
684 find witch selected object this instance belongs to
685 provide support for "copy to selected"
687 selected
= context
.selected_objects
[:]
689 props
= archipack_fence
.datablock(o
)
691 for part
in props
.rail_mat
:
696 def update(self
, context
):
697 props
= self
.find_datablock_in_selection(context
)
698 if props
is not None:
699 props
.update(context
)
702 class archipack_fence_part(PropertyGroup
):
705 ('S_FENCE', 'Straight fence', '', 0),
706 ('C_FENCE', 'Curved fence', '', 1),
711 length
: FloatProperty(
715 unit
='LENGTH', subtype
='DISTANCE',
718 radius
: FloatProperty(
722 unit
='LENGTH', subtype
='DISTANCE',
730 subtype
='ANGLE', unit
='ROTATION',
738 subtype
='ANGLE', unit
='ROTATION',
744 unit
='LENGTH', subtype
='DISTANCE'
747 manipulators
: CollectionProperty(type=archipack_manipulator
)
749 def find_datablock_in_selection(self
, context
):
751 find witch selected object this instance belongs to
752 provide support for "copy to selected"
754 selected
= context
.selected_objects
[:]
756 props
= archipack_fence
.datablock(o
)
757 if props
is not None:
758 for part
in props
.parts
:
763 def update(self
, context
, manipulable_refresh
=False):
764 props
= self
.find_datablock_in_selection(context
)
765 if props
is not None:
766 props
.update(context
, manipulable_refresh
)
768 def draw(self
, layout
, context
, index
):
771 row
.prop(self
, "type", text
=str(index
+ 1))
772 if self
.type in ['C_FENCE']:
774 row
.prop(self
, "radius")
779 row
.prop(self
, "length")
784 class archipack_fence(ArchipackObject
, Manipulable
, PropertyGroup
):
786 parts
: CollectionProperty(type=archipack_fence_part
)
787 user_defined_path
: StringProperty(
791 user_defined_spline
: IntProperty(
797 user_defined_resolution
: IntProperty(
801 default
=12, update
=update_path
803 n_parts
: IntProperty(
806 default
=1, update
=update_manipulators
808 x_offset
: FloatProperty(
810 default
=0.0, precision
=2, step
=1,
811 unit
='LENGTH', subtype
='DISTANCE',
815 radius
: FloatProperty(
819 unit
='LENGTH', subtype
='DISTANCE',
827 subtype
='ANGLE', unit
='ROTATION',
830 angle_limit
: FloatProperty(
835 subtype
='ANGLE', unit
='ROTATION',
836 update
=update_manipulators
838 shape
: EnumProperty(
840 ('RECTANGLE', 'Straight', '', 0),
841 ('CIRCLE', 'Curved ', '', 1)
851 post_spacing
: FloatProperty(
854 default
=1.0, precision
=2, step
=1,
855 unit
='LENGTH', subtype
='DISTANCE',
858 post_x
: FloatProperty(
861 default
=0.04, precision
=2, step
=1,
862 unit
='LENGTH', subtype
='DISTANCE',
865 post_y
: FloatProperty(
868 default
=0.04, precision
=2, step
=1,
869 unit
='LENGTH', subtype
='DISTANCE',
872 post_z
: FloatProperty(
875 default
=1, precision
=2, step
=1,
876 unit
='LENGTH', subtype
='DISTANCE',
879 post_alt
: FloatProperty(
881 default
=0, precision
=2, step
=1,
882 unit
='LENGTH', subtype
='DISTANCE',
885 user_defined_post_enable
: BoolProperty(
890 user_defined_post
: StringProperty(
894 idmat_post
: EnumProperty(
896 items
=materials_enum
,
905 subs_spacing
: FloatProperty(
908 default
=0.10, precision
=2, step
=1,
909 unit
='LENGTH', subtype
='DISTANCE',
912 subs_x
: FloatProperty(
915 default
=0.02, precision
=2, step
=1,
916 unit
='LENGTH', subtype
='DISTANCE',
919 subs_y
: FloatProperty(
922 default
=0.02, precision
=2, step
=1,
923 unit
='LENGTH', subtype
='DISTANCE',
926 subs_z
: FloatProperty(
929 default
=1, precision
=2, step
=1,
930 unit
='LENGTH', subtype
='DISTANCE',
933 subs_alt
: FloatProperty(
935 default
=0, precision
=2, step
=1,
936 unit
='LENGTH', subtype
='DISTANCE',
939 subs_offset_x
: FloatProperty(
941 default
=0.0, precision
=2, step
=1,
942 unit
='LENGTH', subtype
='DISTANCE',
945 subs_bottom
: EnumProperty(
948 ('STEP', 'Follow step', '', 0),
949 ('LINEAR', 'Linear', '', 1),
954 user_defined_subs_enable
: BoolProperty(
959 user_defined_subs
: StringProperty(
963 idmat_subs
: EnumProperty(
965 items
=materials_enum
,
969 panel
: BoolProperty(
974 panel_alt
: FloatProperty(
976 default
=0.25, precision
=2, step
=1,
977 unit
='LENGTH', subtype
='DISTANCE',
980 panel_x
: FloatProperty(
983 default
=0.01, precision
=2, step
=1,
984 unit
='LENGTH', subtype
='DISTANCE',
987 panel_z
: FloatProperty(
990 default
=0.6, precision
=2, step
=1,
991 unit
='LENGTH', subtype
='DISTANCE',
994 panel_dist
: FloatProperty(
997 default
=0.05, precision
=2, step
=1,
998 unit
='LENGTH', subtype
='DISTANCE',
1001 panel_offset_x
: FloatProperty(
1003 default
=0.0, precision
=2, step
=1,
1004 unit
='LENGTH', subtype
='DISTANCE',
1007 idmat_panel
: EnumProperty(
1009 items
=materials_enum
,
1013 rail
: BoolProperty(
1018 rail_n
: IntProperty(
1025 rail_x
: FloatVectorProperty(
1028 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
1029 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
1030 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
1031 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05
1035 precision
=2, step
=1,
1039 rail_z
: FloatVectorProperty(
1042 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
1043 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
1044 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
1045 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05
1049 precision
=2, step
=1,
1053 rail_offset
: FloatVectorProperty(
1056 0, 0, 0, 0, 0, 0, 0, 0,
1057 0, 0, 0, 0, 0, 0, 0, 0,
1058 0, 0, 0, 0, 0, 0, 0, 0,
1062 precision
=2, step
=1,
1066 rail_alt
: FloatVectorProperty(
1069 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
1070 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
1071 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
1072 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
1075 precision
=2, step
=1,
1079 rail_mat
: CollectionProperty(type=archipack_fence_material
)
1081 handrail
: BoolProperty(
1086 handrail_offset
: FloatProperty(
1088 default
=0.0, precision
=2, step
=1,
1089 unit
='LENGTH', subtype
='DISTANCE',
1092 handrail_alt
: FloatProperty(
1094 default
=1.0, precision
=2, step
=1,
1095 unit
='LENGTH', subtype
='DISTANCE',
1098 handrail_extend
: FloatProperty(
1101 default
=0.1, precision
=2, step
=1,
1102 unit
='LENGTH', subtype
='DISTANCE',
1105 handrail_slice
: BoolProperty(
1110 handrail_slice_right
: BoolProperty(
1115 handrail_profil
: EnumProperty(
1118 ('SQUARE', 'Square', '', 0),
1119 ('CIRCLE', 'Circle', '', 1),
1120 ('COMPLEX', 'Circle over square', '', 2)
1125 handrail_x
: FloatProperty(
1128 default
=0.04, precision
=2, step
=1,
1129 unit
='LENGTH', subtype
='DISTANCE',
1132 handrail_y
: FloatProperty(
1135 default
=0.04, precision
=2, step
=1,
1136 unit
='LENGTH', subtype
='DISTANCE',
1139 handrail_radius
: FloatProperty(
1142 default
=0.02, precision
=2, step
=1,
1143 unit
='LENGTH', subtype
='DISTANCE',
1146 idmat_handrail
: EnumProperty(
1148 items
=materials_enum
,
1154 parts_expand
: BoolProperty(
1157 rail_expand
: BoolProperty(
1160 idmats_expand
: BoolProperty(
1163 handrail_expand
: BoolProperty(
1166 post_expand
: BoolProperty(
1169 panel_expand
: BoolProperty(
1172 subs_expand
: BoolProperty(
1176 # Flag to prevent mesh update while making bulk changes over variables
1178 # .auto_update = False
1180 # .auto_update = True
1181 auto_update
: BoolProperty(
1182 options
={'SKIP_SAVE'},
1184 update
=update_manipulators
1187 def setup_manipulators(self
):
1189 if len(self
.manipulators
) == 0:
1190 s
= self
.manipulators
.add()
1191 s
.prop1_name
= "width"
1192 s
= self
.manipulators
.add()
1193 s
.prop1_name
= "height"
1194 s
.normal
= Vector((0, 1, 0))
1196 for i
in range(self
.n_parts
):
1198 n_manips
= len(p
.manipulators
)
1200 s
= p
.manipulators
.add()
1201 s
.type_key
= "ANGLE"
1203 s
= p
.manipulators
.add()
1205 s
.prop1_name
= "length"
1206 s
= p
.manipulators
.add()
1207 # s.type_key = 'SNAP_POINT'
1208 s
.type_key
= 'WALL_SNAP'
1209 s
.prop1_name
= str(i
)
1210 s
.prop2_name
= 'post_z'
1212 def update_parts(self
):
1214 # remove rails materials
1215 for i
in range(len(self
.rail_mat
), self
.rail_n
, -1):
1216 self
.rail_mat
.remove(i
- 1)
1219 for i
in range(len(self
.rail_mat
), self
.rail_n
):
1223 for i
in range(len(self
.parts
), self
.n_parts
, -1):
1224 self
.parts
.remove(i
- 1)
1227 for i
in range(len(self
.parts
), self
.n_parts
):
1229 self
.setup_manipulators()
1231 def interpolate_bezier(self
, pts
, wM
, p0
, p1
, resolution
):
1232 # straight segment, worth testing here
1233 # since this can lower points count by a resolution factor
1234 # use normalized to handle non linear t
1236 pts
.append(wM
@ p0
.co
.to_3d())
1238 v
= (p1
.co
- p0
.co
).normalized()
1239 d1
= (p0
.handle_right
- p0
.co
).normalized()
1240 d2
= (p1
.co
- p1
.handle_left
).normalized()
1241 if d1
== v
and d2
== v
:
1242 pts
.append(wM
@ p0
.co
.to_3d())
1244 seg
= interpolate_bezier(wM
@ p0
.co
,
1245 wM
@ p0
.handle_right
,
1246 wM
@ p1
.handle_left
,
1249 for i
in range(resolution
):
1250 pts
.append(seg
[i
].to_3d())
1252 def from_spline(self
, context
, wM
, resolution
, spline
):
1254 o
= self
.find_in_selection(context
)
1260 tM
.row
[0].normalize()
1261 tM
.row
[1].normalize()
1262 tM
.row
[2].normalize()
1264 if spline
.type == 'POLY':
1265 pt
= spline
.points
[0].co
1266 pts
= [wM
@ p
.co
.to_3d() for p
in spline
.points
]
1267 if spline
.use_cyclic_u
:
1269 elif spline
.type == 'BEZIER':
1270 pt
= spline
.bezier_points
[0].co
1271 points
= spline
.bezier_points
1272 for i
in range(1, len(points
)):
1275 self
.interpolate_bezier(pts
, wM
, p0
, p1
, resolution
)
1276 if spline
.use_cyclic_u
:
1279 self
.interpolate_bezier(pts
, wM
, p0
, p1
, resolution
)
1282 pts
.append(wM
@ points
[-1].co
)
1283 auto_update
= self
.auto_update
1284 self
.auto_update
= False
1286 self
.n_parts
= len(pts
) - 1
1291 for i
, p1
in enumerate(pts
):
1293 da
= atan2(dp
.y
, dp
.x
) - a0
1299 p
.length
= dp
.to_2d().length
1305 self
.auto_update
= auto_update
1307 o
.matrix_world
= tM
@ Matrix
.Translation(pt
)
1309 def update_path(self
, context
):
1310 path
= context
.scene
.objects
.get(self
.user_defined_path
.strip())
1311 if path
is not None and path
.type == 'CURVE':
1312 splines
= path
.data
.splines
1313 if len(splines
) > self
.user_defined_spline
:
1317 self
.user_defined_resolution
,
1318 splines
[self
.user_defined_spline
])
1320 def get_generator(self
):
1321 g
= FenceGenerator(self
.parts
)
1322 for part
in self
.parts
:
1323 # type, radius, da, length
1326 g
.set_offset(self
.x_offset
)
1327 # param_t(da, part_length)
1328 g
.param_t(self
.angle_limit
, self
.post_spacing
)
1331 def update(self
, context
, manipulable_refresh
=False):
1332 o
= self
.find_in_selection(context
, self
.auto_update
)
1337 # clean up manipulators before any data model change
1338 if manipulable_refresh
:
1339 self
.manipulable_disable(context
)
1348 g
= self
.get_generator()
1351 # self.manipulators[1].set_pts([(0, 0, 0), (0, 0, self.height), (1, 0, 0)])
1353 if self
.user_defined_post_enable
:
1354 # user defined posts
1355 user_def_post
= context
.scene
.objects
.get(self
.user_defined_post
.strip())
1356 if user_def_post
is not None and user_def_post
.type == 'MESH':
1357 g
.setup_user_defined_post(user_def_post
, self
.post_x
, self
.post_y
, self
.post_z
)
1360 g
.make_post(0.5 * self
.post_x
, 0.5 * self
.post_y
, self
.post_z
,
1361 self
.post_alt
, self
.x_offset
,
1362 int(self
.idmat_post
), verts
, faces
, matids
, uvs
)
1364 # reset user def posts
1365 g
.user_defined_post
= None
1368 if self
.user_defined_subs_enable
:
1369 user_def_subs
= context
.scene
.objects
.get(self
.user_defined_subs
.strip())
1370 if user_def_subs
is not None and user_def_subs
.type == 'MESH':
1371 g
.setup_user_defined_post(user_def_subs
, self
.subs_x
, self
.subs_y
, self
.subs_z
)
1374 g
.make_subs(0.5 * self
.subs_x
, 0.5 * self
.subs_y
, self
.subs_z
,
1375 self
.post_y
, self
.subs_alt
, self
.subs_spacing
,
1376 self
.x_offset
, self
.subs_offset_x
, int(self
.idmat_subs
), verts
, faces
, matids
, uvs
)
1378 g
.user_defined_post
= None
1381 g
.make_panels(0.5 * self
.panel_x
, self
.panel_z
, self
.post_y
,
1382 self
.panel_alt
, self
.panel_dist
, self
.x_offset
, self
.panel_offset_x
,
1383 int(self
.idmat_panel
), verts
, faces
, matids
, uvs
)
1386 for i
in range(self
.rail_n
):
1387 x
= 0.5 * self
.rail_x
[i
]
1389 rail
= [Vector((-x
, y
)), Vector((-x
, 0)), Vector((x
, 0)), Vector((x
, y
))]
1390 g
.make_profile(rail
, int(self
.rail_mat
[i
].index
), self
.x_offset
- self
.rail_offset
[i
],
1391 self
.rail_alt
[i
], 0, verts
, faces
, matids
, uvs
)
1393 if self
.handrail_profil
== 'COMPLEX':
1394 sx
= self
.handrail_x
1395 sy
= self
.handrail_y
1396 handrail
= [Vector((sx
* x
, sy
* y
)) for x
, y
in [
1397 (-0.28, 1.83), (-0.355, 1.77), (-0.415, 1.695), (-0.46, 1.605), (-0.49, 1.51), (-0.5, 1.415),
1398 (-0.49, 1.315), (-0.46, 1.225), (-0.415, 1.135), (-0.355, 1.06), (-0.28, 1.0), (-0.255, 0.925),
1399 (-0.33, 0.855), (-0.5, 0.855), (-0.5, 0.0), (0.5, 0.0), (0.5, 0.855), (0.33, 0.855), (0.255, 0.925),
1400 (0.28, 1.0), (0.355, 1.06), (0.415, 1.135), (0.46, 1.225), (0.49, 1.315), (0.5, 1.415),
1401 (0.49, 1.51), (0.46, 1.605), (0.415, 1.695), (0.355, 1.77), (0.28, 1.83), (0.19, 1.875),
1402 (0.1, 1.905), (0.0, 1.915), (-0.095, 1.905), (-0.19, 1.875)]]
1404 elif self
.handrail_profil
== 'SQUARE':
1405 x
= 0.5 * self
.handrail_x
1407 handrail
= [Vector((-x
, y
)), Vector((-x
, 0)), Vector((x
, 0)), Vector((x
, y
))]
1408 elif self
.handrail_profil
== 'CIRCLE':
1409 r
= self
.handrail_radius
1410 handrail
= [Vector((r
* sin(0.1 * -a
* pi
), r
* (0.5 + cos(0.1 * -a
* pi
)))) for a
in range(0, 20)]
1413 g
.make_profile(handrail
, int(self
.idmat_handrail
), self
.x_offset
- self
.handrail_offset
,
1414 self
.handrail_alt
, self
.handrail_extend
, verts
, faces
, matids
, uvs
)
1416 bmed
.buildmesh(context
, o
, verts
, faces
, matids
=matids
, uvs
=uvs
, weld
=True, clean
=True)
1418 # enable manipulators rebuild
1419 if manipulable_refresh
:
1420 self
.manipulable_refresh
= True
1423 self
.restore_context(context
)
1425 def manipulable_setup(self
, context
):
1428 this one assume context.active_object is the instance this
1429 data belongs to, failing to do so will result in wrong
1430 manipulators set on active object
1432 self
.manipulable_disable(context
)
1434 o
= context
.active_object
1436 self
.setup_manipulators()
1438 for i
, part
in enumerate(self
.parts
):
1439 if i
>= self
.n_parts
:
1444 self
.manip_stack
.append(part
.manipulators
[0].setup(context
, o
, part
))
1446 # length / radius + angle
1447 self
.manip_stack
.append(part
.manipulators
[1].setup(context
, o
, part
))
1450 self
.manip_stack
.append(part
.manipulators
[2].setup(context
, o
, self
))
1452 for m
in self
.manipulators
:
1453 self
.manip_stack
.append(m
.setup(context
, o
, self
))
1456 class ARCHIPACK_PT_fence(Panel
):
1457 bl_idname
= "ARCHIPACK_PT_fence"
1459 bl_space_type
= 'VIEW_3D'
1460 bl_region_type
= 'UI'
1461 bl_category
= 'Archipack'
1464 def poll(cls
, context
):
1465 return archipack_fence
.filter(context
.active_object
)
1467 def draw(self
, context
):
1468 prop
= archipack_fence
.datablock(context
.active_object
)
1471 scene
= context
.scene
1472 layout
= self
.layout
1473 row
= layout
.row(align
=True)
1474 row
.operator('archipack.fence_manipulate', icon
='VIEW_PAN')
1476 # box.label(text="Styles")
1477 row
= box
.row(align
=True)
1478 row
.operator("archipack.fence_preset_menu", text
=bpy
.types
.ARCHIPACK_OT_fence_preset_menu
.bl_label
)
1479 row
.operator("archipack.fence_preset", text
="", icon
='ADD')
1480 row
.operator("archipack.fence_preset", text
="", icon
='REMOVE').remove_active
= True
1482 row
= box
.row(align
=True)
1483 row
.operator("archipack.fence_curve_update", text
="", icon
='FILE_REFRESH')
1484 row
.prop_search(prop
, "user_defined_path", scene
, "objects", text
="", icon
='OUTLINER_OB_CURVE')
1485 if prop
.user_defined_path
!= "":
1486 box
.prop(prop
, 'user_defined_spline')
1487 box
.prop(prop
, 'user_defined_resolution')
1488 box
.prop(prop
, 'angle_limit')
1489 box
.prop(prop
, 'x_offset')
1492 if prop
.parts_expand
:
1493 row
.prop(prop
, 'parts_expand', icon
="TRIA_DOWN", text
="Parts", emboss
=False)
1494 box
.prop(prop
, 'n_parts')
1495 for i
, part
in enumerate(prop
.parts
):
1496 part
.draw(layout
, context
, i
)
1498 row
.prop(prop
, 'parts_expand', icon
="TRIA_RIGHT", text
="Parts", emboss
=False)
1501 row
= box
.row(align
=True)
1502 if prop
.handrail_expand
:
1503 row
.prop(prop
, 'handrail_expand', icon
="TRIA_DOWN", text
="Handrail", emboss
=False)
1505 row
.prop(prop
, 'handrail_expand', icon
="TRIA_RIGHT", text
="Handrail", emboss
=False)
1507 row
.prop(prop
, 'handrail')
1509 if prop
.handrail_expand
:
1510 box
.prop(prop
, 'handrail_alt')
1511 box
.prop(prop
, 'handrail_offset')
1512 box
.prop(prop
, 'handrail_extend')
1513 box
.prop(prop
, 'handrail_profil')
1514 if prop
.handrail_profil
!= 'CIRCLE':
1515 box
.prop(prop
, 'handrail_x')
1516 box
.prop(prop
, 'handrail_y')
1518 box
.prop(prop
, 'handrail_radius')
1519 row
= box
.row(align
=True)
1520 row
.prop(prop
, 'handrail_slice')
1523 row
= box
.row(align
=True)
1524 if prop
.post_expand
:
1525 row
.prop(prop
, 'post_expand', icon
="TRIA_DOWN", text
="Post", emboss
=False)
1527 row
.prop(prop
, 'post_expand', icon
="TRIA_RIGHT", text
="Post", emboss
=False)
1528 row
.prop(prop
, 'post')
1529 if prop
.post_expand
:
1530 box
.prop(prop
, 'post_spacing')
1531 box
.prop(prop
, 'post_x')
1532 box
.prop(prop
, 'post_y')
1533 box
.prop(prop
, 'post_z')
1534 box
.prop(prop
, 'post_alt')
1535 row
= box
.row(align
=True)
1536 row
.prop(prop
, 'user_defined_post_enable', text
="")
1537 row
.prop_search(prop
, "user_defined_post", scene
, "objects", text
="")
1540 row
= box
.row(align
=True)
1541 if prop
.subs_expand
:
1542 row
.prop(prop
, 'subs_expand', icon
="TRIA_DOWN", text
="Subs", emboss
=False)
1544 row
.prop(prop
, 'subs_expand', icon
="TRIA_RIGHT", text
="Subs", emboss
=False)
1546 row
.prop(prop
, 'subs')
1547 if prop
.subs_expand
:
1548 box
.prop(prop
, 'subs_spacing')
1549 box
.prop(prop
, 'subs_x')
1550 box
.prop(prop
, 'subs_y')
1551 box
.prop(prop
, 'subs_z')
1552 box
.prop(prop
, 'subs_alt')
1553 box
.prop(prop
, 'subs_offset_x')
1554 row
= box
.row(align
=True)
1555 row
.prop(prop
, 'user_defined_subs_enable', text
="")
1556 row
.prop_search(prop
, "user_defined_subs", scene
, "objects", text
="")
1559 row
= box
.row(align
=True)
1560 if prop
.panel_expand
:
1561 row
.prop(prop
, 'panel_expand', icon
="TRIA_DOWN", text
="Panels", emboss
=False)
1563 row
.prop(prop
, 'panel_expand', icon
="TRIA_RIGHT", text
="Panels", emboss
=False)
1564 row
.prop(prop
, 'panel')
1565 if prop
.panel_expand
:
1566 box
.prop(prop
, 'panel_dist')
1567 box
.prop(prop
, 'panel_x')
1568 box
.prop(prop
, 'panel_z')
1569 box
.prop(prop
, 'panel_alt')
1570 box
.prop(prop
, 'panel_offset_x')
1573 row
= box
.row(align
=True)
1574 if prop
.rail_expand
:
1575 row
.prop(prop
, 'rail_expand', icon
="TRIA_DOWN", text
="Rails", emboss
=False)
1577 row
.prop(prop
, 'rail_expand', icon
="TRIA_RIGHT", text
="Rails", emboss
=False)
1578 row
.prop(prop
, 'rail')
1579 if prop
.rail_expand
:
1580 box
.prop(prop
, 'rail_n')
1581 for i
in range(prop
.rail_n
):
1583 box
.label(text
="Rail " + str(i
+ 1))
1584 box
.prop(prop
, 'rail_x', index
=i
)
1585 box
.prop(prop
, 'rail_z', index
=i
)
1586 box
.prop(prop
, 'rail_alt', index
=i
)
1587 box
.prop(prop
, 'rail_offset', index
=i
)
1588 box
.prop(prop
.rail_mat
[i
], 'index', text
="")
1593 if prop
.idmats_expand
:
1594 row
.prop(prop
, 'idmats_expand', icon
="TRIA_DOWN", text
="Materials", emboss
=False)
1595 box
.prop(prop
, 'idmat_handrail')
1596 box
.prop(prop
, 'idmat_panel')
1597 box
.prop(prop
, 'idmat_post')
1598 box
.prop(prop
, 'idmat_subs')
1600 row
.prop(prop
, 'idmats_expand', icon
="TRIA_RIGHT", text
="Materials", emboss
=False)
1602 # ------------------------------------------------------------------
1603 # Define operator class to create object
1604 # ------------------------------------------------------------------
1607 class ARCHIPACK_OT_fence(ArchipackCreateTool
, Operator
):
1608 bl_idname
= "archipack.fence"
1610 bl_description
= "Fence"
1611 bl_category
= 'Archipack'
1612 bl_options
= {'REGISTER', 'UNDO'}
1614 def create(self
, context
):
1616 m
= bpy
.data
.meshes
.new("Fence")
1617 o
= bpy
.data
.objects
.new("Fence", m
)
1618 d
= m
.archipack_fence
.add()
1619 # make manipulators selectable
1621 d
.manipulable_selectable
= True
1622 self
.link_object_to_scene(context
, o
)
1623 o
.select_set(state
=True)
1624 context
.view_layer
.objects
.active
= o
1627 self
.add_material(o
)
1630 def execute(self
, context
):
1631 if context
.mode
== "OBJECT":
1632 bpy
.ops
.object.select_all(action
="DESELECT")
1633 o
= self
.create(context
)
1634 o
.location
= context
.scene
.cursor
.location
1635 o
.select_set(state
=True)
1636 context
.view_layer
.objects
.active
= o
1640 self
.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1641 return {'CANCELLED'}
1644 # ------------------------------------------------------------------
1645 # Define operator class to create object
1646 # ------------------------------------------------------------------
1648 class ARCHIPACK_OT_fence_curve_update(Operator
):
1649 bl_idname
= "archipack.fence_curve_update"
1650 bl_label
= "Fence curve update"
1651 bl_description
= "Update fence data from curve"
1652 bl_category
= 'Archipack'
1653 bl_options
= {'REGISTER', 'UNDO'}
1656 def poll(self
, context
):
1657 return archipack_fence
.filter(context
.active_object
)
1659 def draw(self
, context
):
1660 layout
= self
.layout
1662 row
.label(text
="Use Properties panel (N) to define parms", icon
='INFO')
1664 def execute(self
, context
):
1665 if context
.mode
== "OBJECT":
1666 d
= archipack_fence
.datablock(context
.active_object
)
1667 d
.update_path(context
)
1670 self
.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1671 return {'CANCELLED'}
1674 class ARCHIPACK_OT_fence_from_curve(ArchipackCreateTool
, Operator
):
1675 bl_idname
= "archipack.fence_from_curve"
1676 bl_label
= "Fence curve"
1677 bl_description
= "Create a fence from a curve"
1678 bl_category
= 'Archipack'
1679 bl_options
= {'REGISTER', 'UNDO'}
1682 def poll(self
, context
):
1683 return context
.active_object
is not None and context
.active_object
.type == 'CURVE'
1685 def draw(self
, context
):
1686 layout
= self
.layout
1688 row
.label(text
="Use Properties panel (N) to define parms", icon
='INFO')
1690 def create(self
, context
):
1692 curve
= context
.active_object
1693 for i
, spline
in enumerate(curve
.data
.splines
):
1694 bpy
.ops
.archipack
.fence('INVOKE_DEFAULT', auto_manipulate
=False)
1695 o
= context
.active_object
1696 d
= archipack_fence
.datablock(o
)
1697 d
.auto_update
= False
1698 d
.user_defined_spline
= i
1699 d
.user_defined_path
= curve
.name
1700 d
.auto_update
= True
1703 def execute(self
, context
):
1704 if context
.mode
== "OBJECT":
1705 bpy
.ops
.object.select_all(action
="DESELECT")
1706 o
= self
.create(context
)
1708 o
.select_set(state
=True)
1709 context
.view_layer
.objects
.active
= o
1713 self
.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1714 return {'CANCELLED'}
1716 # ------------------------------------------------------------------
1717 # Define operator class to manipulate object
1718 # ------------------------------------------------------------------
1721 class ARCHIPACK_OT_fence_manipulate(Operator
):
1722 bl_idname
= "archipack.fence_manipulate"
1723 bl_label
= "Manipulate"
1724 bl_description
= "Manipulate"
1725 bl_options
= {'REGISTER', 'UNDO'}
1728 def poll(self
, context
):
1729 return archipack_fence
.filter(context
.active_object
)
1731 def invoke(self
, context
, event
):
1732 d
= archipack_fence
.datablock(context
.active_object
)
1733 d
.manipulable_invoke(context
)
1737 # ------------------------------------------------------------------
1738 # Define operator class to load / save presets
1739 # ------------------------------------------------------------------
1742 class ARCHIPACK_OT_fence_preset_menu(PresetMenuOperator
, Operator
):
1743 bl_description
= "Show Fence Presets"
1744 bl_idname
= "archipack.fence_preset_menu"
1745 bl_label
= "Fence Styles"
1746 preset_subdir
= "archipack_fence"
1749 class ARCHIPACK_OT_fence_preset(ArchipackPreset
, Operator
):
1750 """Add a Fence Preset"""
1751 bl_idname
= "archipack.fence_preset"
1752 bl_label
= "Add Fence Style"
1753 preset_menu
= "ARCHIPACK_OT_fence_preset_menu"
1756 def blacklist(self
):
1757 return ['manipulators', 'n_parts', 'parts', 'user_defined_path', 'user_defined_spline']
1761 bpy
.utils
.register_class(archipack_fence_material
)
1762 bpy
.utils
.register_class(archipack_fence_part
)
1763 bpy
.utils
.register_class(archipack_fence
)
1764 Mesh
.archipack_fence
= CollectionProperty(type=archipack_fence
)
1765 bpy
.utils
.register_class(ARCHIPACK_OT_fence_preset_menu
)
1766 bpy
.utils
.register_class(ARCHIPACK_PT_fence
)
1767 bpy
.utils
.register_class(ARCHIPACK_OT_fence
)
1768 bpy
.utils
.register_class(ARCHIPACK_OT_fence_preset
)
1769 bpy
.utils
.register_class(ARCHIPACK_OT_fence_manipulate
)
1770 bpy
.utils
.register_class(ARCHIPACK_OT_fence_from_curve
)
1771 bpy
.utils
.register_class(ARCHIPACK_OT_fence_curve_update
)
1775 bpy
.utils
.unregister_class(archipack_fence_material
)
1776 bpy
.utils
.unregister_class(archipack_fence_part
)
1777 bpy
.utils
.unregister_class(archipack_fence
)
1778 del Mesh
.archipack_fence
1779 bpy
.utils
.unregister_class(ARCHIPACK_OT_fence_preset_menu
)
1780 bpy
.utils
.unregister_class(ARCHIPACK_PT_fence
)
1781 bpy
.utils
.unregister_class(ARCHIPACK_OT_fence
)
1782 bpy
.utils
.unregister_class(ARCHIPACK_OT_fence_preset
)
1783 bpy
.utils
.unregister_class(ARCHIPACK_OT_fence_manipulate
)
1784 bpy
.utils
.unregister_class(ARCHIPACK_OT_fence_from_curve
)
1785 bpy
.utils
.unregister_class(ARCHIPACK_OT_fence_curve_update
)