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 # ----------------------------------------------------------
28 from math
import cos
, sin
, tan
, sqrt
, atan2
, pi
29 from mathutils
import Vector
35 index: array associate each y with a coord circle and a x
36 x = array of x of unique points in the profil relative to origin (0, 0) is bottom left
37 y = array of y of all points in the profil relative to origin (0, 0) is bottom left
38 idmat = array of material index for each segment
39 when path is not closed, start and end caps are generated
41 shape is the loft profile
70 Side Caps (like glass for window):
73 y = [0,1,1, 0.75, 0.25, 0]
74 index = [0, 0,1,1,1,1]
85 def __init__(self
, closed_shape
, index
, x
, y
, idmat
, side_cap_front
=-1, side_cap_back
=-1, closed_path
=True,
86 subdiv_x
=0, subdiv_y
=0, user_path_verts
=0, user_path_uv_v
=None):
88 self
.closed_shape
= closed_shape
89 self
.closed_path
= closed_path
94 self
.side_cap_front
= side_cap_front
95 self
.side_cap_back
= side_cap_back
96 self
.subdiv_x
= subdiv_x
97 self
.subdiv_y
= subdiv_y
98 self
.user_path_verts
= user_path_verts
99 self
.user_path_uv_v
= user_path_uv_v
106 def profil_faces(self
):
108 number of faces for each section
110 if self
.closed_shape
:
113 return len(self
.y
) - 1
118 uvs of profil (absolute value)
120 x
= [self
.x
[i
] for i
in self
.index
]
122 y
= [y
for y
in self
.y
]
127 for i
in range(len(self
.index
)):
130 uv
+= sqrt(dx
* dx
+ dy
* dy
)
134 def path_sections(self
, steps
, path_type
):
136 number of verts and faces sections along path
139 if path_type
in ['QUADRI', 'RECTANGLE']:
140 n_path_verts
= 4 + self
.subdiv_x
+ 2 * self
.subdiv_y
142 n_path_verts
+= self
.subdiv_x
143 elif path_type
in ['ROUND', 'ELLIPSIS']:
144 n_path_verts
= steps
+ 3
145 elif path_type
== 'CIRCLE':
147 elif path_type
== 'TRIANGLE':
149 elif path_type
== 'PENTAGON':
151 elif path_type
== 'USER_DEFINED':
152 n_path_verts
= self
.user_path_verts
154 n_path_faces
= n_path_verts
156 n_path_faces
= n_path_verts
- 1
157 return n_path_verts
, n_path_faces
159 def n_verts(self
, steps
, path_type
):
160 n_path_verts
, n_path_faces
= self
.path_sections(steps
, path_type
)
161 return self
.n_pts
* n_path_verts
163 ############################
165 ############################
167 def _intersect_line(self
, center
, basis
, x
):
168 """ upper intersection of line parallel to y axis and a triangle
169 where line is given by x origin
170 top by center, basis size as float
171 return float y of upper intersection point
173 center.x and center.y are absolute
174 a 0 center.x lie on half size
175 a 0 center.y lie on basis
182 return center
.y
+ dx
* p
184 def _intersect_triangle(self
, center
, basis
, x
):
185 """ upper intersection of line parallel to y axis and a triangle
186 where line is given by x origin
187 top by center, basis size as float
188 return float y of upper intersection point
190 center.x and center.y are absolute
191 a 0 center.x lie on half size
192 a 0 center.y lie on basis
196 sx
= 0.5 * basis
- center
.x
199 sx
= 0.5 * basis
+ center
.x
203 return center
.y
+ dx
* p
205 def _intersect_circle(self
, center
, radius
, x
):
206 """ upper intersection of line parallel to y axis and a circle
207 where line is given by x origin
208 circle by center, radius as float
209 return float y of upper intersection point, float angle
212 d
= (radius
* radius
) - (dx
* dx
)
220 return center
.y
+ y
, atan2(y
, dx
)
222 def _intersect_elipsis(self
, center
, radius
, x
):
223 """ upper intersection of line parallel to y axis and an ellipsis
224 where line is given by x origin
225 circle by center, radius.x and radius.y semimajor and seminimor axis (half width and height) as float
226 return float y of upper intersection point, float angle
230 A
= 1 / radius
.y
/ radius
.y
231 C
= d2
/ radius
.x
/ radius
.x
- 1
240 d
= (radius
.x
* radius
.x
) - d2
242 return center
.y
+ y0
, atan2(y
, dx
)
244 def _intersect_arc(self
, center
, radius
, x_left
, x_right
):
245 y0
, a0
= self
._intersect
_circle
(center
, radius
.x
, x_left
)
246 y1
, a1
= self
._intersect
_circle
(center
, radius
.x
, x_right
)
252 return y0
, y1
, a0
, da
254 def _intersect_arc_elliptic(self
, center
, radius
, x_left
, x_right
):
255 y0
, a0
= self
._intersect
_elipsis
(center
, radius
, x_left
)
256 y1
, a1
= self
._intersect
_elipsis
(center
, radius
, x_right
)
262 return y0
, y1
, a0
, da
264 def _get_ellispe_coords(self
, steps
, offset
, center
, origin
, size
, radius
, x
, pivot
, bottom_y
=0):
266 Rectangle with single arc on top
268 x_left
= size
.x
/ 2 * (pivot
- 1) + x
269 x_right
= size
.x
/ 2 * (pivot
+ 1) - x
270 cx
= center
.x
- origin
.x
271 cy
= offset
.y
+ center
.y
- origin
.y
272 y0
, y1
, a0
, da
= self
._intersect
_arc
_elliptic
(center
, radius
, origin
.x
+ x_left
, origin
.x
+ x_right
)
277 coords
.append((offset
.x
+ x_left
, offset
.y
+ x
+ bottom_y
))
279 coords
.append((offset
.x
+ x_left
, offset
.y
+ bottom_y
))
281 coords
.append((offset
.x
+ x_left
, offset
.y
+ y0
- origin
.y
))
282 for i
in range(1, steps
):
284 coords
.append((offset
.x
+ cx
+ cos(a
) * radius
.x
, cy
+ sin(a
) * radius
.y
))
286 coords
.append((offset
.x
+ x_right
, offset
.y
+ y1
- origin
.y
))
289 coords
.append((offset
.x
+ x_right
, offset
.y
+ x
+ bottom_y
))
291 coords
.append((offset
.x
+ x_right
, offset
.y
+ bottom_y
))
294 def _get_arc_coords(self
, steps
, offset
, center
, origin
, size
, radius
, x
, pivot
, bottom_y
=0):
296 Rectangle with single arc on top
298 x_left
= size
.x
/ 2 * (pivot
- 1) + x
299 x_right
= size
.x
/ 2 * (pivot
+ 1) - x
300 cx
= offset
.x
+ center
.x
- origin
.x
301 cy
= offset
.y
+ center
.y
- origin
.y
302 y0
, y1
, a0
, da
= self
._intersect
_arc
(center
, radius
, origin
.x
+ x_left
, origin
.x
+ x_right
)
308 coords
.append((offset
.x
+ x_left
, offset
.y
+ x
+ bottom_y
))
310 coords
.append((offset
.x
+ x_left
, offset
.y
+ bottom_y
))
313 coords
.append((offset
.x
+ x_left
, offset
.y
+ y0
- origin
.y
))
315 for i
in range(1, steps
):
317 coords
.append((cx
+ cos(a
) * radius
.x
, cy
+ sin(a
) * radius
.x
))
320 coords
.append((offset
.x
+ x_right
, offset
.y
+ y1
- origin
.y
))
324 coords
.append((offset
.x
+ x_right
, offset
.y
+ x
+ bottom_y
))
326 coords
.append((offset
.x
+ x_right
, offset
.y
+ bottom_y
))
330 def _get_circle_coords(self
, steps
, offset
, center
, origin
, radius
):
334 cx
= offset
.x
+ center
.x
- origin
.x
335 cy
= offset
.y
+ center
.y
- origin
.y
337 return [(cx
+ cos(i
* a
) * radius
.x
, cy
+ sin(i
* a
) * radius
.x
) for i
in range(steps
)]
339 def _get_rectangular_coords(self
, offset
, size
, x
, pivot
, bottom_y
=0):
342 x_left
= offset
.x
+ size
.x
/ 2 * (pivot
- 1) + x
343 x_right
= offset
.x
+ size
.x
/ 2 * (pivot
+ 1) - x
346 y0
= offset
.y
+ x
+ bottom_y
348 y0
= offset
.y
+ bottom_y
349 y1
= offset
.y
+ size
.y
- x
351 dy
= (y1
- y0
) / (1 + self
.subdiv_y
)
352 dx
= (x_right
- x_left
) / (1 + self
.subdiv_x
)
355 # coords.append((x_left, y0))
358 for i
in range(self
.subdiv_y
+ 1):
359 coords
.append((x_left
, y0
+ i
* dy
))
362 # coords.append((x_left, y1))
365 for i
in range(self
.subdiv_x
+ 1):
366 coords
.append((x_left
+ dx
* i
, y1
))
369 # coords.append((x_right, y1))
371 for i
in range(self
.subdiv_y
+ 1):
372 coords
.append((x_right
, y1
- i
* dy
))
376 for i
in range(self
.subdiv_x
+ 1):
377 coords
.append((x_right
- dx
* i
, y0
))
380 coords
.append((x_right
, y0
))
384 def _get_vertical_rectangular_trapezoid_coords(self
, offset
, center
, origin
, size
, basis
, x
, pivot
, bottom_y
=0):
386 Rectangular trapezoid vertical
387 basis is the full width of a triangular area the trapezoid lie into
388 center.y is the height of triagular area from top
389 center.x is the offset from basis center
396 x_left
= size
.x
/ 2 * (pivot
- 1) + x
397 x_right
= size
.x
/ 2 * (pivot
+ 1) - x
398 sx
= x
* sqrt(basis
* basis
+ center
.y
* center
.y
) / basis
399 dy
= size
.y
+ offset
.y
- sx
400 y0
= self
._intersect
_line
(center
, basis
, origin
.x
+ x_left
)
401 y1
= self
._intersect
_line
(center
, basis
, origin
.x
+ x_right
)
404 coords
.append((offset
.x
+ x_left
, offset
.y
+ x
+ bottom_y
))
406 coords
.append((offset
.x
+ x_left
, offset
.y
+ bottom_y
))
408 coords
.append((offset
.x
+ x_left
, dy
- y0
))
410 coords
.append((offset
.x
+ x_right
, dy
- y1
))
413 coords
.append((offset
.x
+ x_right
, offset
.y
+ x
+ bottom_y
))
415 coords
.append((offset
.x
+ x_right
, offset
.y
+ bottom_y
))
418 def _get_horizontal_rectangular_trapezoid_coords(self
, offset
, center
, origin
, size
, basis
, x
, pivot
, bottom_y
=0):
420 Rectangular trapeze horizontal
421 basis is the full width of a triangular area the trapezoid lie into
422 center.y is the height of triagular area from top to basis
423 center.x is the offset from basis center
428 TODO: correct implementation
430 raise NotImplementedError
432 def _get_pentagon_coords(self
, offset
, center
, origin
, size
, basis
, x
, pivot
, bottom_y
=0):
434 TODO: correct implementation
440 raise NotImplementedError
442 def _get_triangle_coords(self
, offset
, center
, origin
, size
, basis
, x
, pivot
, bottom_y
=0):
444 x_left
= offset
.x
+ size
.x
/ 2 * (pivot
- 1) + x
445 x_right
= offset
.x
+ size
.x
/ 2 * (pivot
+ 1) - x
449 coords
.append((x_left
, offset
.y
+ x
+ bottom_y
))
451 coords
.append((x_left
, offset
.y
+ bottom_y
))
453 coords
.append((center
.x
, offset
.y
+ center
.y
))
456 coords
.append((x_right
, offset
.y
+ x
+ bottom_y
))
458 coords
.append((x_right
, offset
.y
+ bottom_y
))
461 def _get_horizontal_coords(self
, offset
, size
, x
, pivot
):
463 x_left
= offset
.x
+ size
.x
/ 2 * (pivot
- 1)
464 x_right
= offset
.x
+ size
.x
/ 2 * (pivot
+ 1)
466 coords
.append((x_left
, offset
.y
+ x
))
468 coords
.append((x_right
, offset
.y
+ x
))
471 def _get_vertical_coords(self
, offset
, size
, x
, pivot
):
473 x_left
= offset
.x
+ size
.x
/ 2 * (pivot
- 1) + x
475 coords
.append((x_left
, offset
.y
+ size
.y
))
477 coords
.append((x_left
, offset
.y
))
480 def choose_a_shape_in_tri(self
, center
, origin
, size
, basis
, pivot
):
482 Choose which shape inside either a tri or a pentagon
484 cx
= (0.5 * basis
+ center
.x
) - origin
.x
485 cy
= center
.y
- origin
.y
486 x_left
= size
.x
/ 2 * (pivot
- 1)
487 x_right
= size
.x
/ 2 * (pivot
+ 1)
488 y0
= self
.intersect_triangle(cx
, cy
, basis
, x_left
)
489 y1
= self
.intersect_triangle(cx
, cy
, basis
, x_right
)
490 if (y0
== 0 and y1
== 0) or ((y0
== 0 or y1
== 0) and (y0
== cy
or y1
== cy
)):
492 elif x_right
<= cx
or x_left
>= cx
:
493 # single side of triangle
494 # may be horizontal or vertical rectangular trapezoid
495 # horizontal if size.y < center.y
498 # both sides of triangle
499 # may be horizontal trapezoid or pentagon
500 # horizontal trapezoid if size.y < center.y
503 ############################
505 ############################
507 def vertices(self
, steps
, offset
, center
, origin
, size
, radius
,
508 angle_y
, pivot
, shape_z
=None, path_type
='ROUND', axis
='XZ'):
512 shape_z
= [0 for x
in self
.x
]
513 if path_type
== 'ROUND':
514 coords
= [self
._get
_arc
_coords
(steps
, offset
, center
, origin
,
515 size
, Vector((radius
.x
- x
, 0)), x
, pivot
, shape_z
[i
]) for i
, x
in enumerate(self
.x
)]
516 elif path_type
== 'ELLIPSIS':
517 coords
= [self
._get
_ellispe
_coords
(steps
, offset
, center
, origin
,
518 size
, Vector((radius
.x
- x
, radius
.y
- x
)), x
, pivot
, shape_z
[i
]) for i
, x
in enumerate(self
.x
)]
519 elif path_type
== 'QUADRI':
520 coords
= [self
._get
_vertical
_rectangular
_trapezoid
_coords
(offset
, center
, origin
,
521 size
, radius
.x
, x
, pivot
) for i
, x
in enumerate(self
.x
)]
522 elif path_type
== 'HORIZONTAL':
523 coords
= [self
._get
_horizontal
_coords
(offset
, size
, x
, pivot
)
524 for i
, x
in enumerate(self
.x
)]
525 elif path_type
== 'VERTICAL':
526 coords
= [self
._get
_vertical
_coords
(offset
, size
, x
, pivot
)
527 for i
, x
in enumerate(self
.x
)]
528 elif path_type
== 'CIRCLE':
529 coords
= [self
._get
_circle
_coords
(steps
, offset
, center
, origin
, Vector((radius
.x
- x
, 0)))
530 for i
, x
in enumerate(self
.x
)]
532 coords
= [self
._get
_rectangular
_coords
(offset
, size
, x
, pivot
, shape_z
[i
])
533 for i
, x
in enumerate(self
.x
)]
534 # vertical panel (as for windows)
536 for i
in range(len(coords
[0])):
537 for j
, p
in enumerate(self
.index
):
540 verts
.append((x
, y
, z
))
541 # horizontal panel (table and so on)
543 for i
in range(len(coords
[0])):
544 for j
, p
in enumerate(self
.index
):
547 verts
.append((x
, y
, z
))
550 ############################
552 ############################
554 def _faces_cap(self
, faces
, n_path_verts
, offset
):
555 if self
.closed_shape
and not self
.closed_path
:
556 last_point
= offset
+ self
.n_pts
* n_path_verts
- 1
557 faces
.append(tuple([offset
+ i
for i
in range(self
.n_pts
)]))
558 faces
.append(tuple([last_point
- i
for i
in range(self
.n_pts
)]))
560 def _faces_closed(self
, n_path_faces
, offset
):
563 for i
in range(n_path_faces
):
564 k0
= offset
+ i
* n_pts
565 if self
.closed_path
and i
== n_path_faces
- 1:
569 for j
in range(n_pts
- 1):
570 faces
.append((k1
+ j
, k1
+ j
+ 1, k0
+ j
+ 1, k0
+ j
))
572 faces
.append((k1
+ n_pts
- 1, k1
, k0
, k0
+ n_pts
- 1))
575 def _faces_open(self
, n_path_faces
, offset
):
578 for i
in range(n_path_faces
):
579 k0
= offset
+ i
* n_pts
580 if self
.closed_path
and i
== n_path_faces
- 1:
584 for j
in range(n_pts
- 1):
585 faces
.append((k1
+ j
, k1
+ j
+ 1, k0
+ j
+ 1, k0
+ j
))
588 def _faces_side(self
, faces
, n_path_verts
, start
, reverse
, offset
):
590 vf
= [offset
+ start
+ n_pts
* f
for f
in range(n_path_verts
)]
592 faces
.append(tuple(reversed(vf
)))
594 faces
.append(tuple(vf
))
596 def faces(self
, steps
, offset
=0, path_type
='ROUND'):
597 n_path_verts
, n_path_faces
= self
.path_sections(steps
, path_type
)
598 if self
.closed_shape
:
599 faces
= self
._faces
_closed
(n_path_faces
, offset
)
601 faces
= self
._faces
_open
(n_path_faces
, offset
)
602 if self
.side_cap_front
> -1:
603 self
._faces
_side
(faces
, n_path_verts
, self
.side_cap_front
, False, offset
)
604 if self
.side_cap_back
> -1:
605 self
._faces
_side
(faces
, n_path_verts
, self
.side_cap_back
, True, offset
)
606 self
._faces
_cap
(faces
, n_path_verts
, offset
)
609 ############################
611 ############################
613 def uv(self
, steps
, center
, origin
, size
, radius
, angle_y
, pivot
, x
, x_cap
, path_type
='ROUND'):
615 n_path_verts
, n_path_faces
= self
.path_sections(steps
, path_type
)
616 if path_type
in ['ROUND', 'ELLIPSIS']:
617 x_left
= size
.x
/ 2 * (pivot
- 1) + x
618 x_right
= size
.x
/ 2 * (pivot
+ 1) - x
619 if path_type
== 'ELLIPSIS':
620 y0
, y1
, a0
, da
= self
._intersect
_arc
_elliptic
(center
, radius
, x_left
, x_right
)
622 y0
, y1
, a0
, da
= self
._intersect
_arc
(center
, radius
, x_left
, x_right
)
623 uv_r
= abs(da
) * radius
.x
/ steps
624 uv_v
= [uv_r
for i
in range(steps
)]
625 uv_v
.insert(0, y0
- origin
.y
)
626 uv_v
.append(y1
- origin
.y
)
628 elif path_type
== 'USER_DEFINED':
629 uv_v
= self
.user_path_uv_v
630 elif path_type
== 'CIRCLE':
631 uv_r
= 2 * pi
* radius
.x
/ steps
632 uv_v
= [uv_r
for i
in range(steps
+ 1)]
633 elif path_type
== 'QUADRI':
634 dy
= 0.5 * tan(angle_y
) * size
.x
635 uv_v
= [size
.y
- dy
, size
.x
, size
.y
+ dy
, size
.x
]
636 elif path_type
== 'HORIZONTAL':
638 elif path_type
== 'VERTICAL':
641 dx
= size
.x
/ (1 + self
.subdiv_x
)
642 dy
= size
.y
/ (1 + self
.subdiv_y
)
644 for i
in range(self
.subdiv_y
+ 1):
645 uv_v
.append(dy
* (i
+ 1))
646 for i
in range(self
.subdiv_x
+ 1):
647 uv_v
.append(dx
* (i
+ 1))
648 for i
in range(self
.subdiv_y
+ 1):
649 uv_v
.append(dy
* (i
+ 1))
650 for i
in range(self
.subdiv_x
+ 1):
651 uv_v
.append(dx
* (i
+ 1))
652 # uv_v = [size.y, size.x, size.y, size.x]
655 if self
.closed_shape
:
658 n_pts
= self
.n_pts
- 1
661 for i
in range(n_path_faces
):
663 for j
in range(n_pts
):
666 uvs
.append([(u0
, v1
), (u1
, v1
), (u1
, v0
), (u0
, v0
)])
668 if self
.side_cap_back
> -1 or self
.side_cap_front
> -1:
669 if path_type
== 'ROUND':
670 # rectangle with top part round
671 coords
= self
._get
_arc
_coords
(steps
, Vector((0, 0, 0)), center
,
672 origin
, size
, Vector((radius
.x
- x_cap
, 0)), x_cap
, pivot
, x_cap
)
673 elif path_type
== 'CIRCLE':
675 coords
= self
._get
_circle
_coords
(steps
, Vector((0, 0, 0)), center
,
676 origin
, Vector((radius
.x
- x_cap
, 0)))
677 elif path_type
== 'ELLIPSIS':
678 coords
= self
._get
_ellispe
_coords
(steps
, Vector((0, 0, 0)), center
,
679 origin
, size
, Vector((radius
.x
- x_cap
, radius
.y
- x_cap
)), x_cap
, pivot
, x_cap
)
680 elif path_type
== 'QUADRI':
681 coords
= self
._get
_vertical
_rectangular
_trapezoid
_coords
(Vector((0, 0, 0)), center
,
682 origin
, size
, radius
.x
, x_cap
, pivot
)
683 # coords = self._get_trapezoidal_coords(0, origin, size, angle_y, x_cap, pivot, x_cap)
685 coords
= self
._get
_rectangular
_coords
(Vector((0, 0, 0)), size
, x_cap
, pivot
, 0)
686 if self
.side_cap_front
> -1:
687 uvs
.append(list(coords
))
688 if self
.side_cap_back
> -1:
689 uvs
.append(list(reversed(coords
)))
691 if self
.closed_shape
and not self
.closed_path
:
692 coords
= [(self
.x
[self
.index
[i
]], y
) for i
, y
in enumerate(self
.y
)]
694 uvs
.append(list(reversed(coords
)))
697 ############################
699 ############################
701 def mat(self
, steps
, cap_front_id
, cap_back_id
, path_type
='ROUND'):
702 n_path_verts
, n_path_faces
= self
.path_sections(steps
, path_type
)
703 n_profil_faces
= self
.profil_faces
705 for i
in range(n_path_faces
):
706 for f
in range(n_profil_faces
):
707 idmat
.append(self
.idmat
[f
])
708 if self
.side_cap_front
> -1:
709 idmat
.append(cap_front_id
)
710 if self
.side_cap_back
> -1:
711 idmat
.append(cap_back_id
)
712 if self
.closed_shape
and not self
.closed_path
:
713 idmat
.append(self
.idmat
[0])
714 idmat
.append(self
.idmat
[0])