1 # SPDX-FileCopyrightText: 2010-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # Author: Buerbaum Martin (Pontiac)
8 from math
import sin
, cos
, tan
, pi
, radians
9 from bpy
.types
import Operator
10 from bpy
.props
import (
16 from bpy_extras
import object_utils
18 # Create a new mesh (object) from verts/edges/faces.
19 # verts/edges/faces ... List of vertices/edges/faces for the
20 # new mesh (as used in from_pydata)
21 # name ... Name of the new mesh (& object)
23 def create_mesh(context
, verts
, edges
, faces
, name
):
25 mesh
= bpy
.data
.meshes
.new(name
)
27 # Make a mesh from a list of verts/edges/faces.
28 mesh
.from_pydata(verts
, edges
, faces
)
30 # Update mesh geometry after adding stuff.
35 # A very simple "bridge" tool.
37 def createFaces(vertIdx1
, vertIdx2
, closed
=False, flipped
=False):
40 if not vertIdx1
or not vertIdx2
:
43 if len(vertIdx1
) < 2 and len(vertIdx2
) < 2:
47 if (len(vertIdx1
) != len(vertIdx2
)):
48 if (len(vertIdx1
) == 1 and len(vertIdx2
) > 1):
56 # Bridge the start with the end.
63 face
.append(vertIdx1
[total
- 1])
67 face
= [vertIdx2
[0], vertIdx1
[0]]
69 face
.append(vertIdx1
[total
- 1])
70 face
.append(vertIdx2
[total
- 1])
73 # Bridge the rest of the faces.
74 for num
in range(total
- 1):
77 face
= [vertIdx2
[num
], vertIdx1
[0], vertIdx2
[num
+ 1]]
79 face
= [vertIdx2
[num
], vertIdx1
[num
],
80 vertIdx1
[num
+ 1], vertIdx2
[num
+ 1]]
84 face
= [vertIdx1
[0], vertIdx2
[num
], vertIdx2
[num
+ 1]]
86 face
= [vertIdx1
[num
], vertIdx2
[num
],
87 vertIdx2
[num
+ 1], vertIdx1
[num
+ 1]]
93 # Create the vertices and polygons for a simple elbow (bent pipe)
94 def ElbowJointParameters():
95 ElbowJointParameters
= [
102 return ElbowJointParameters
104 class AddElbowJoint(Operator
, object_utils
.AddObjectHelper
):
105 bl_idname
= "mesh.primitive_elbow_joint_add"
106 bl_label
= "Add Pipe Elbow"
107 bl_description
= "Construct an elbow pipe mesh"
108 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
110 ElbowJoint
: BoolProperty(name
= "ElbowJoint",
112 description
= "ElbowJoint")
114 #### change properties
115 change
: BoolProperty(name
= "Change",
117 description
= "change ElbowJoint")
119 radius
: FloatProperty(
121 description
="The radius of the pipe",
129 description
="Number of vertices (divisions)",
130 default
=32, min=3, max=256
132 angle
: FloatProperty(
134 description
="The angle of the branching pipe (i.e. the 'arm' - "
135 "Measured from the center line of the main pipe",
136 default
=radians(45.0),
141 startLength
: FloatProperty(
143 description
="Length of the beginning of the pipe",
149 endLength
: FloatProperty(
151 description
="Length of the end of the pipe",
158 def draw(self
, context
):
162 box
.prop(self
, 'radius')
163 box
.prop(self
, 'div')
164 box
.prop(self
, 'angle')
165 box
.prop(self
, 'startLength')
166 box
.prop(self
, 'endLength')
168 if self
.change
== False:
169 # generic transform props
171 box
.prop(self
, 'align', expand
=True)
172 box
.prop(self
, 'location', expand
=True)
173 box
.prop(self
, 'rotation', expand
=True)
175 def execute(self
, context
):
176 # turn off 'Enter Edit Mode'
177 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
178 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
185 startLength
= self
.startLength
186 endLength
= self
.endLength
191 loop1
= [] # The starting circle
192 loop2
= [] # The elbow circle
193 loop3
= [] # The end circle
195 # Create start circle
196 for vertIdx
in range(div
):
197 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
198 locX
= sin(curVertAngle
)
199 locY
= cos(curVertAngle
)
201 loop1
.append(len(verts
))
202 verts
.append([locX
* radius
, locY
* radius
, locZ
])
204 # Create deformed joint circle
205 for vertIdx
in range(div
):
206 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
207 locX
= sin(curVertAngle
)
208 locY
= cos(curVertAngle
)
209 locZ
= locX
* tan(angle
/ 2.0)
210 loop2
.append(len(verts
))
211 verts
.append([locX
* radius
, locY
* radius
, locZ
* radius
])
214 baseEndLocX
= -endLength
* sin(angle
)
215 baseEndLocZ
= endLength
* cos(angle
)
216 for vertIdx
in range(div
):
217 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
219 locX
= sin(curVertAngle
) * radius
220 locY
= cos(curVertAngle
) * radius
224 locZ
= locX
* cos(pi
/ 2.0 - angle
)
225 locX
= locX
* sin(pi
/ 2.0 - angle
)
227 loop3
.append(len(verts
))
228 # Translate and add circle vertices to the list.
229 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
232 faces
.extend(createFaces(loop1
, loop2
, closed
=True))
233 faces
.extend(createFaces(loop2
, loop3
, closed
=True))
235 if bpy
.context
.mode
== "OBJECT":
236 if (context
.selected_objects
!= []) and context
.active_object
and \
237 (context
.active_object
.data
is not None) and ('ElbowJoint' in context
.active_object
.data
.keys()) and \
238 (self
.change
== True):
239 obj
= context
.active_object
241 oldmeshname
= obj
.data
.name
242 mesh
= create_mesh(context
, verts
, [], faces
, "Elbow Joint")
244 for material
in oldmesh
.materials
:
245 obj
.data
.materials
.append(material
)
246 bpy
.data
.meshes
.remove(oldmesh
)
247 obj
.data
.name
= oldmeshname
249 mesh
= create_mesh(context
, verts
, [], faces
, "Elbow Joint")
250 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
254 obj
.data
["ElbowJoint"] = True
255 obj
.data
["change"] = False
256 for prm
in ElbowJointParameters():
257 obj
.data
[prm
] = getattr(self
, prm
)
259 if bpy
.context
.mode
== "EDIT_MESH":
260 active_object
= context
.active_object
261 name_active_object
= active_object
.name
262 bpy
.ops
.object.mode_set(mode
='OBJECT')
263 mesh
= create_mesh(context
, verts
, [], faces
, "TMP")
264 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
266 active_object
.select_set(True)
267 bpy
.context
.view_layer
.objects
.active
= active_object
268 bpy
.ops
.object.join()
269 context
.active_object
.name
= name_active_object
270 bpy
.ops
.object.mode_set(mode
='EDIT')
272 if use_enter_edit_mode
:
273 bpy
.ops
.object.mode_set(mode
= 'EDIT')
275 # restore pre operator state
276 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode
281 # Create the vertices and polygons for a simple tee (T) joint
282 # The base arm of the T can be positioned in an angle if needed though
283 def TeeJointParameters():
284 TeeJointParameters
= [
292 return TeeJointParameters
294 class AddTeeJoint(Operator
, object_utils
.AddObjectHelper
):
295 bl_idname
= "mesh.primitive_tee_joint_add"
296 bl_label
= "Add Pipe Tee-Joint"
297 bl_description
= "Construct a tee-joint pipe mesh"
298 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
300 TeeJoint
: BoolProperty(name
= "TeeJoint",
302 description
= "TeeJoint")
304 #### change properties
305 change
: BoolProperty(name
= "Change",
307 description
= "change TeeJoint")
309 radius
: FloatProperty(
311 description
="The radius of the pipe",
319 description
="Number of vertices (divisions)",
324 angle
: FloatProperty(
326 description
="The angle of the branching pipe (i.e. the 'arm' - "
327 "Measured from the center line of the main pipe",
328 default
=radians(90.0),
333 startLength
: FloatProperty(
335 description
="Length of the beginning of the"
336 " main pipe (the straight one)",
342 endLength
: FloatProperty(
344 description
="Length of the end of the"
345 " main pipe (the straight one)",
351 branchLength
: FloatProperty(
353 description
="Length of the arm pipe (the bent one)",
360 def draw(self
, context
):
364 box
.prop(self
, 'radius')
365 box
.prop(self
, 'div')
366 box
.prop(self
, 'angle')
367 box
.prop(self
, 'startLength')
368 box
.prop(self
, 'endLength')
369 box
.prop(self
, 'branchLength')
371 if self
.change
== False:
372 # generic transform props
374 box
.prop(self
, 'align', expand
=True)
375 box
.prop(self
, 'location', expand
=True)
376 box
.prop(self
, 'rotation', expand
=True)
378 def execute(self
, context
):
379 # turn off 'Enter Edit Mode'
380 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
381 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
388 startLength
= self
.startLength
389 endLength
= self
.endLength
390 branchLength
= self
.branchLength
393 # Odd vertice number not supported (yet)
394 self
.report({'INFO'}, "Odd vertices number is not yet supported")
400 # List of vert indices of each cross section
401 loopMainStart
= [] # Vert indices for the beginning of the main pipe
402 loopJoint1
= [] # Vert indices for joint that is used to connect the joint & loopMainStart
403 loopJoint2
= [] # Vert indices for joint that is used to connect the joint & loopArm
404 loopJoint3
= [] # Vert index for joint that is used to connect the joint & loopMainEnd
405 loopArm
= [] # Vert indices for the end of the arm
406 loopMainEnd
= [] # Vert indices for the end of the main pipe.
408 # Create start circle (main pipe)
409 for vertIdx
in range(div
):
410 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
411 locX
= sin(curVertAngle
)
412 locY
= cos(curVertAngle
)
414 loopMainStart
.append(len(verts
))
415 verts
.append([locX
* radius
, locY
* radius
, locZ
])
417 # Create deformed joint circle
420 for vertIdx
in range(div
):
421 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
422 locX
= sin(curVertAngle
)
423 locY
= cos(curVertAngle
)
426 vertTemp1
= len(verts
)
427 if vertIdx
== div
/ 2:
428 # @todo: This will possibly break if we
429 # ever support odd divisions.
430 vertTemp2
= len(verts
)
432 loopJoint1
.append(len(verts
))
433 if (vertIdx
< div
/ 2):
434 # Straight side of main pipe.
436 loopJoint3
.append(len(verts
))
439 locZ
= locX
* tan(angle
/ 2.0)
440 loopJoint2
.append(len(verts
))
442 verts
.append([locX
* radius
, locY
* radius
, locZ
* radius
])
444 # Create 2. deformed joint (half-)circle
446 for vertIdx
in range(div
):
447 if (vertIdx
> div
/ 2):
448 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
449 locX
= sin(curVertAngle
)
450 locY
= -cos(curVertAngle
)
451 locZ
= -(radius
* locX
* tan((pi
- angle
) / 2.0))
452 loopTemp
.append(len(verts
))
453 verts
.append([locX
* radius
, locY
* radius
, locZ
])
455 loopTemp2
= loopTemp
[:]
459 loopTemp
.append(vertTemp1
)
461 loopJoint2
.extend(loopTemp
)
465 loopTemp2
.append(vertTemp2
)
467 loopJoint3
.extend(loopTemp2
)
469 # Create end circle (branching pipe)
470 baseEndLocX
= -branchLength
* sin(angle
)
471 baseEndLocZ
= branchLength
* cos(angle
)
472 for vertIdx
in range(div
):
473 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
475 locX
= sin(curVertAngle
) * radius
476 locY
= cos(curVertAngle
) * radius
480 locZ
= locX
* cos(pi
/ 2.0 - angle
)
481 locX
= locX
* sin(pi
/ 2.0 - angle
)
483 loopArm
.append(len(verts
))
485 # Add translated circle.
486 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
488 # Create end circle (main pipe)
489 for vertIdx
in range(div
):
490 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
491 locX
= sin(curVertAngle
)
492 locY
= cos(curVertAngle
)
494 loopMainEnd
.append(len(verts
))
495 verts
.append([locX
* radius
, locY
* radius
, locZ
])
498 faces
.extend(createFaces(loopMainStart
, loopJoint1
, closed
=True))
499 faces
.extend(createFaces(loopJoint2
, loopArm
, closed
=True))
500 faces
.extend(createFaces(loopJoint3
, loopMainEnd
, closed
=True))
502 if bpy
.context
.mode
== "OBJECT":
503 if (context
.selected_objects
!= []) and context
.active_object
and \
504 (context
.active_object
.data
is not None) and ('TeeJoint' in context
.active_object
.data
.keys()) and \
505 (self
.change
== True):
506 obj
= context
.active_object
508 oldmeshname
= obj
.data
.name
509 mesh
= create_mesh(context
, verts
, [], faces
, "Tee Joint")
511 for material
in oldmesh
.materials
:
512 obj
.data
.materials
.append(material
)
513 bpy
.data
.meshes
.remove(oldmesh
)
514 obj
.data
.name
= oldmeshname
516 mesh
= create_mesh(context
, verts
, [], faces
, "Tee Joint")
517 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
521 obj
.data
["TeeJoint"] = True
522 obj
.data
["change"] = False
523 for prm
in TeeJointParameters():
524 obj
.data
[prm
] = getattr(self
, prm
)
526 if bpy
.context
.mode
== "EDIT_MESH":
527 active_object
= context
.active_object
528 name_active_object
= active_object
.name
529 bpy
.ops
.object.mode_set(mode
='OBJECT')
530 mesh
= create_mesh(context
, verts
, [], faces
, "TMP")
531 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
533 active_object
.select_set(True)
534 bpy
.context
.view_layer
.objects
.active
= active_object
535 bpy
.ops
.object.join()
536 context
.active_object
.name
= name_active_object
537 bpy
.ops
.object.mode_set(mode
='EDIT')
539 if use_enter_edit_mode
:
540 bpy
.ops
.object.mode_set(mode
= 'EDIT')
542 # restore pre operator state
543 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode
547 def WyeJointParameters():
548 WyeJointParameters
= [
557 return WyeJointParameters
559 class AddWyeJoint(Operator
, object_utils
.AddObjectHelper
):
560 bl_idname
= "mesh.primitive_wye_joint_add"
561 bl_label
= "Add Pipe Wye-Joint"
562 bl_description
= "Construct a wye-joint pipe mesh"
563 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
565 WyeJoint
: BoolProperty(name
= "WyeJoint",
567 description
= "WyeJoint")
569 #### change properties
570 change
: BoolProperty(name
= "Change",
572 description
= "change WyeJoint")
574 radius
: FloatProperty(
576 description
="The radius of the pipe",
584 description
="Number of vertices (divisions)",
589 angle1
: FloatProperty(
591 description
="The angle of the 1. branching pipe "
592 "(measured from the center line of the main pipe)",
593 default
=radians(45.0),
598 angle2
: FloatProperty(
600 description
="The angle of the 2. branching pipe "
601 "(measured from the center line of the main pipe) ",
602 default
=radians(45.0),
607 startLength
: FloatProperty(
609 description
="Length of the beginning of the"
610 " main pipe (the straight one)",
616 branch1Length
: FloatProperty(
618 description
="Length of the 1. arm",
624 branch2Length
: FloatProperty(
626 description
="Length of the 2. arm",
633 def draw(self
, context
):
637 box
.prop(self
, 'radius')
638 box
.prop(self
, 'div')
639 box
.prop(self
, 'angle1')
640 box
.prop(self
, 'angle2')
641 box
.prop(self
, 'startLength')
642 box
.prop(self
, 'branch1Length')
643 box
.prop(self
, 'branch2Length')
645 if self
.change
== False:
646 # generic transform props
648 box
.prop(self
, 'align', expand
=True)
649 box
.prop(self
, 'location', expand
=True)
650 box
.prop(self
, 'rotation', expand
=True)
652 def execute(self
, context
):
653 # turn off 'Enter Edit Mode'
654 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
655 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
663 startLength
= self
.startLength
664 branch1Length
= self
.branch1Length
665 branch2Length
= self
.branch2Length
668 # Odd vertice number not supported (yet)
669 self
.report({'INFO'}, "Odd vertices number is not yet supported")
675 # List of vert indices of each cross section
676 loopMainStart
= [] # Vert indices for the beginning of the main pipe
677 loopJoint1
= [] # Vert index for joint that is used to connect the joint & loopMainStart
678 loopJoint2
= [] # Vert index for joint that is used to connect the joint & loopArm1
679 loopJoint3
= [] # Vert index for joint that is used to connect the joint & loopArm2
680 loopArm1
= [] # Vert idxs for end of the 1. arm
681 loopArm2
= [] # Vert idxs for end of the 2. arm
683 # Create start circle
684 for vertIdx
in range(div
):
685 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
686 locX
= sin(curVertAngle
)
687 locY
= cos(curVertAngle
)
689 loopMainStart
.append(len(verts
))
690 verts
.append([locX
* radius
, locY
* radius
, locZ
])
692 # Create deformed joint circle
695 for vertIdx
in range(div
):
696 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
697 locX
= sin(curVertAngle
)
698 locY
= cos(curVertAngle
)
701 vertTemp2
= len(verts
)
702 if vertIdx
== div
/ 2:
703 # @todo: This will possibly break if we
704 # ever support odd divisions.
705 vertTemp1
= len(verts
)
707 loopJoint1
.append(len(verts
))
708 if (vertIdx
> div
/ 2):
709 locZ
= locX
* tan(angle1
/ 2.0)
710 loopJoint2
.append(len(verts
))
712 locZ
= locX
* tan(-angle2
/ 2.0)
713 loopJoint3
.append(len(verts
))
715 verts
.append([locX
* radius
, locY
* radius
, locZ
* radius
])
717 # Create 2. deformed joint (half-)circle
719 angleJoint
= (angle2
- angle1
) / 2.0
720 for vertIdx
in range(div
):
721 if (vertIdx
> div
/ 2):
722 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
724 locX
= (-sin(curVertAngle
) * sin(angleJoint
) / sin(angle2
- angleJoint
))
725 locY
= -cos(curVertAngle
)
726 locZ
= (-(sin(curVertAngle
) * cos(angleJoint
) / sin(angle2
- angleJoint
)))
728 loopTemp
.append(len(verts
))
729 verts
.append([locX
* radius
, locY
* radius
, locZ
* radius
])
731 loopTemp2
= loopTemp
[:]
734 loopTemp
.append(vertTemp1
)
736 loopTemp
.append(vertTemp2
)
738 loopJoint2
.extend(loopTemp
)
743 loopJoint3
.extend(loopTemp2
)
745 # Create end circle (1. branching pipe)
746 baseEndLocX
= -branch1Length
* sin(angle1
)
747 baseEndLocZ
= branch1Length
* cos(angle1
)
748 for vertIdx
in range(div
):
749 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
751 locX
= sin(curVertAngle
) * radius
752 locY
= cos(curVertAngle
) * radius
756 locZ
= locX
* cos(pi
/ 2.0 - angle1
)
757 locX
= locX
* sin(pi
/ 2.0 - angle1
)
759 loopArm1
.append(len(verts
))
760 # Add translated circle.
761 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
763 # Create end circle (2. branching pipe)
764 baseEndLocX
= branch2Length
* sin(angle2
)
765 baseEndLocZ
= branch2Length
* cos(angle2
)
766 for vertIdx
in range(div
):
767 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
769 locX
= sin(curVertAngle
) * radius
770 locY
= cos(curVertAngle
) * radius
774 locZ
= locX
* cos(pi
/ 2.0 + angle2
)
775 locX
= locX
* sin(pi
/ 2.0 + angle2
)
777 loopArm2
.append(len(verts
))
778 # Add translated circle
779 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
782 faces
.extend(createFaces(loopMainStart
, loopJoint1
, closed
=True))
783 faces
.extend(createFaces(loopJoint2
, loopArm1
, closed
=True))
784 faces
.extend(createFaces(loopJoint3
, loopArm2
, closed
=True))
786 if bpy
.context
.mode
== "OBJECT":
787 if (context
.selected_objects
!= []) and context
.active_object
and \
788 (context
.active_object
.data
is not None) and ('WyeJoint' in context
.active_object
.data
.keys()) and \
789 (self
.change
== True):
790 obj
= context
.active_object
792 oldmeshname
= obj
.data
.name
793 mesh
= create_mesh(context
, verts
, [], faces
, "Wye Joint")
795 for material
in oldmesh
.materials
:
796 obj
.data
.materials
.append(material
)
797 bpy
.data
.meshes
.remove(oldmesh
)
798 obj
.data
.name
= oldmeshname
800 mesh
= create_mesh(context
, verts
, [], faces
, "Wye Joint")
801 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
805 obj
.data
["WyeJoint"] = True
806 obj
.data
["change"] = False
807 for prm
in WyeJointParameters():
808 obj
.data
[prm
] = getattr(self
, prm
)
810 if bpy
.context
.mode
== "EDIT_MESH":
811 active_object
= context
.active_object
812 name_active_object
= active_object
.name
813 bpy
.ops
.object.mode_set(mode
='OBJECT')
814 mesh
= create_mesh(context
, verts
, [], faces
, "TMP")
815 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
817 active_object
.select_set(True)
818 bpy
.context
.view_layer
.objects
.active
= active_object
819 bpy
.ops
.object.join()
820 context
.active_object
.name
= name_active_object
821 bpy
.ops
.object.mode_set(mode
='EDIT')
823 if use_enter_edit_mode
:
824 bpy
.ops
.object.mode_set(mode
= 'EDIT')
826 # restore pre operator state
827 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode
832 # Create the vertices and polygons for a cross (+ or X) pipe joint
833 def CrossJointParameters():
834 CrossJointParameters
= [
845 return CrossJointParameters
847 class AddCrossJoint(Operator
, object_utils
.AddObjectHelper
):
848 bl_idname
= "mesh.primitive_cross_joint_add"
849 bl_label
= "Add Pipe Cross-Joint"
850 bl_description
= "Construct a cross-joint pipe mesh"
851 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
853 CrossJoint
: BoolProperty(name
= "CrossJoint",
855 description
= "CrossJoint")
857 #### change properties
858 change
: BoolProperty(name
= "Change",
860 description
= "change CrossJoint")
862 radius
: FloatProperty(
864 description
="The radius of the pipe",
872 description
="Number of vertices (divisions)",
877 angle1
: FloatProperty(
879 description
="The angle of the 1. arm (from the main axis)",
880 default
=radians(90.0),
885 angle2
: FloatProperty(name
="Angle 2",
886 description
="The angle of the 2. arm (from the main axis)",
887 default
=radians(90.0),
892 angle3
: FloatProperty(name
="Angle 3 (center)",
893 description
="The angle of the center arm (from the main axis)",
894 default
=radians(0.0),
899 startLength
: FloatProperty(
901 description
="Length of the beginning of the "
902 "main pipe (the straight one)",
908 branch1Length
: FloatProperty(name
="Length Arm 1",
909 description
="Length of the 1. arm",
915 branch2Length
: FloatProperty(
917 description
="Length of the 2. arm",
923 branch3Length
: FloatProperty(
924 name
="Length Arm 3 (center)",
925 description
="Length of the center arm",
932 def draw(self
, context
):
936 box
.prop(self
, 'radius')
937 box
.prop(self
, 'div')
938 box
.prop(self
, 'angle1')
939 box
.prop(self
, 'angle2')
940 box
.prop(self
, 'angle3')
941 box
.prop(self
, 'startLength')
942 box
.prop(self
, 'branch1Length')
943 box
.prop(self
, 'branch2Length')
944 box
.prop(self
, 'branch3Length')
946 if self
.change
== False:
947 # generic transform props
949 box
.prop(self
, 'align', expand
=True)
950 box
.prop(self
, 'location', expand
=True)
951 box
.prop(self
, 'rotation', expand
=True)
953 def execute(self
, context
):
954 # turn off 'Enter Edit Mode'
955 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
956 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
965 startLength
= self
.startLength
966 branch1Length
= self
.branch1Length
967 branch2Length
= self
.branch2Length
968 branch3Length
= self
.branch3Length
970 # Odd vertice number not supported (yet)
971 self
.report({'INFO'}, "Odd vertices number is not yet supported")
977 # List of vert indices of each cross section
978 loopMainStart
= [] # Vert indices for the beginning of the main pipe
979 loopJoint1
= [] # Vert index for joint that is used to connect the joint & loopMainStart
980 loopJoint2
= [] # Vert index for joint that is used to connect the joint & loopArm1
981 loopJoint3
= [] # Vert index for joint that is used to connect the joint & loopArm2
982 loopJoint4
= [] # Vert index for joint that is used to connect the joint & loopArm3
983 loopArm1
= [] # Vert idxs for the end of the 1. arm
984 loopArm2
= [] # Vert idxs for the end of the 2. arm
985 loopArm3
= [] # Vert idxs for the center arm end
987 # Create start circle
988 for vertIdx
in range(div
):
989 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
990 locX
= sin(curVertAngle
)
991 locY
= cos(curVertAngle
)
993 loopMainStart
.append(len(verts
))
994 verts
.append([locX
* radius
, locY
* radius
, locZ
])
996 # Create 1. deformed joint circle
999 for vertIdx
in range(div
):
1000 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
1001 locX
= sin(curVertAngle
)
1002 locY
= cos(curVertAngle
)
1005 vertTemp2
= len(verts
)
1006 if vertIdx
== div
/ 2:
1007 # @todo: This will possibly break if we
1008 # ever support odd divisions.
1009 vertTemp1
= len(verts
)
1011 loopJoint1
.append(len(verts
))
1012 if (vertIdx
> div
/ 2):
1013 locZ
= locX
* tan(angle1
/ 2.0)
1014 loopJoint2
.append(len(verts
))
1016 locZ
= locX
* tan(-angle2
/ 2.0)
1017 loopJoint3
.append(len(verts
))
1019 verts
.append([locX
* radius
, locY
* radius
, locZ
* radius
])
1021 # Create 2. deformed joint circle
1024 angleJoint1
= (angle1
- angle3
) / 2.0
1025 angleJoint2
= (angle2
+ angle3
) / 2.0
1026 for vertIdx
in range(div
):
1027 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
1029 # Skip pole vertices
1030 # @todo: This will possibly break if
1031 # we ever support odd divisions
1032 if not (vertIdx
== 0) and not (vertIdx
== div
/ 2):
1034 if (vertIdx
> div
/ 2):
1035 angleJoint
= angleJoint1
1038 loopTempA
.append(len(verts
))
1041 angleJoint
= angleJoint2
1044 loopTempB
.append(len(verts
))
1046 locX
= (sin(curVertAngle
) * sin(angleJoint
) / sin(angle
- angleJoint
))
1047 locY
= -cos(curVertAngle
)
1048 locZ
= (Z
* (sin(curVertAngle
) * cos(angleJoint
) / sin(angle
- angleJoint
)))
1050 verts
.append([locX
* radius
, locY
* radius
, locZ
* radius
])
1052 loopTempA2
= loopTempA
[:]
1053 loopTempB2
= loopTempB
[:]
1054 loopTempB3
= loopTempB
[:]
1057 loopTempA
.append(vertTemp1
)
1059 loopTempA
.append(vertTemp2
)
1060 loopJoint2
.reverse()
1061 loopJoint2
.extend(loopTempA
)
1062 loopJoint2
.reverse()
1065 loopJoint3
.extend(loopTempB3
)
1068 loopTempA2
.append(vertTemp1
)
1069 loopTempA2
.reverse()
1070 loopTempB2
.append(vertTemp2
)
1071 loopJoint4
.extend(reversed(loopTempB2
))
1072 loopJoint4
.extend(loopTempA2
)
1074 # Create end circle (1. branching pipe)
1075 baseEndLocX
= -branch1Length
* sin(angle1
)
1076 baseEndLocZ
= branch1Length
* cos(angle1
)
1077 for vertIdx
in range(div
):
1078 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
1080 locX
= sin(curVertAngle
) * radius
1081 locY
= cos(curVertAngle
) * radius
1085 locZ
= locX
* cos(pi
/ 2.0 - angle1
)
1086 locX
= locX
* sin(pi
/ 2.0 - angle1
)
1088 loopArm1
.append(len(verts
))
1089 # Add translated circle.
1090 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
1092 # Create end circle (2. branching pipe)
1093 baseEndLocX
= branch2Length
* sin(angle2
)
1094 baseEndLocZ
= branch2Length
* cos(angle2
)
1095 for vertIdx
in range(div
):
1096 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
1098 locX
= sin(curVertAngle
) * radius
1099 locY
= cos(curVertAngle
) * radius
1103 locZ
= locX
* cos(pi
/ 2.0 + angle2
)
1104 locX
= locX
* sin(pi
/ 2.0 + angle2
)
1106 loopArm2
.append(len(verts
))
1107 # Add translated circle
1108 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
1110 # Create end circle (center pipe)
1111 baseEndLocX
= branch3Length
* sin(angle3
)
1112 baseEndLocZ
= branch3Length
* cos(angle3
)
1113 for vertIdx
in range(div
):
1114 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
1116 locX
= sin(curVertAngle
) * radius
1117 locY
= cos(curVertAngle
) * radius
1121 locZ
= locX
* cos(pi
/ 2.0 + angle3
)
1122 locX
= locX
* sin(pi
/ 2.0 + angle3
)
1124 loopArm3
.append(len(verts
))
1125 # Add translated circle
1126 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
1129 faces
.extend(createFaces(loopMainStart
, loopJoint1
, closed
=True))
1130 faces
.extend(createFaces(loopJoint2
, loopArm1
, closed
=True))
1131 faces
.extend(createFaces(loopJoint3
, loopArm2
, closed
=True))
1132 faces
.extend(createFaces(loopJoint4
, loopArm3
, closed
=True))
1134 if bpy
.context
.mode
== "OBJECT":
1135 if (context
.selected_objects
!= []) and context
.active_object
and \
1136 (context
.active_object
.data
is not None) and ('CrossJoint' in context
.active_object
.data
.keys()) and \
1137 (self
.change
== True):
1138 obj
= context
.active_object
1140 oldmeshname
= obj
.data
.name
1141 mesh
= create_mesh(context
, verts
, [], faces
, "Cross Joint")
1143 for material
in oldmesh
.materials
:
1144 obj
.data
.materials
.append(material
)
1145 bpy
.data
.meshes
.remove(oldmesh
)
1146 obj
.data
.name
= oldmeshname
1148 mesh
= create_mesh(context
, verts
, [], faces
, "Cross Joint")
1149 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
1153 obj
.data
["CrossJoint"] = True
1154 obj
.data
["change"] = False
1155 for prm
in CrossJointParameters():
1156 obj
.data
[prm
] = getattr(self
, prm
)
1158 if bpy
.context
.mode
== "EDIT_MESH":
1159 active_object
= context
.active_object
1160 name_active_object
= active_object
.name
1161 bpy
.ops
.object.mode_set(mode
='OBJECT')
1162 mesh
= create_mesh(context
, verts
, [], faces
, "TMP")
1163 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
1164 obj
.select_set(True)
1165 active_object
.select_set(True)
1166 bpy
.context
.view_layer
.objects
.active
= active_object
1167 bpy
.ops
.object.join()
1168 context
.active_object
.name
= name_active_object
1169 bpy
.ops
.object.mode_set(mode
='EDIT')
1171 if use_enter_edit_mode
:
1172 bpy
.ops
.object.mode_set(mode
= 'EDIT')
1174 # restore pre operator state
1175 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode
1180 # Create the vertices and polygons for a regular n-joint
1181 def NJointParameters():
1182 NJointParameters
= [
1188 return NJointParameters
1190 class AddNJoint(Operator
, object_utils
.AddObjectHelper
):
1191 bl_idname
= "mesh.primitive_n_joint_add"
1192 bl_label
= "Add Pipe N-Joint"
1193 bl_description
= "Construct a n-joint pipe mesh"
1194 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
1196 NJoint
: BoolProperty(name
= "NJoint",
1198 description
= "NJoint")
1200 #### change properties
1201 change
: BoolProperty(name
= "Change",
1203 description
= "change NJoint")
1205 radius
: FloatProperty(
1207 description
="The radius of the pipe",
1215 description
="Number of vertices (divisions)",
1220 number
: IntProperty(
1221 name
="Arms / Joints",
1222 description
="Number of joints / arms",
1227 length
: FloatProperty(
1229 description
="Length of each joint / arm",
1236 def draw(self
, context
):
1237 layout
= self
.layout
1240 box
.prop(self
, 'radius')
1241 box
.prop(self
, 'div')
1242 box
.prop(self
, 'number')
1243 box
.prop(self
, 'length')
1245 if self
.change
== False:
1246 # generic transform props
1248 box
.prop(self
, 'align', expand
=True)
1249 box
.prop(self
, 'location', expand
=True)
1250 box
.prop(self
, 'rotation', expand
=True)
1252 def execute(self
, context
):
1253 # turn off 'Enter Edit Mode'
1254 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
1255 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
1257 radius
= self
.radius
1259 number
= self
.number
1260 length
= self
.length
1263 # Odd vertice number not supported (yet)
1264 self
.report({'INFO'}, "Odd vertices number is not yet supported")
1265 return {'CANCELLED'}
1268 return {'CANCELLED'}
1273 loopsEndCircles
= []
1274 loopsJointsTemp
= []
1280 angleDiv
= (2.0 * pi
/ number
)
1282 # Create vertices for the end circles
1283 for num
in range(number
):
1285 # Create start circle
1286 angle
= num
* angleDiv
1288 baseEndLocX
= length
* sin(angle
)
1289 baseEndLocZ
= length
* cos(angle
)
1290 for vertIdx
in range(div
):
1291 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
1293 locX
= sin(curVertAngle
) * radius
1294 locY
= cos(curVertAngle
) * radius
1298 locZ
= locX
* cos(pi
/ 2.0 + angle
)
1299 locX
= locX
* sin(pi
/ 2.0 + angle
)
1301 circle
.append(len(verts
))
1302 # Add translated circle
1303 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
1305 loopsEndCircles
.append(circle
)
1307 # Create vertices for the joint circles
1309 for vertIdx
in range(div
):
1310 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
1311 locX
= sin(curVertAngle
)
1312 locY
= cos(curVertAngle
)
1315 # Store pole vertices
1318 vertTemp2
= len(verts
)
1321 elif vertIdx
== div
/ 2:
1322 # @todo: This will possibly break if we
1323 # ever support odd divisions
1325 vertTemp1
= len(verts
)
1330 if (vertIdx
> div
/ 2):
1331 locZ
= -locX
* tan((pi
- angleDiv
) / 2.0)
1332 loopJoint
.append(len(verts
))
1335 cosAng
= cos(-angle
)
1336 sinAng
= sin(-angle
)
1337 LocXnew
= locX
* cosAng
- locZ
* sinAng
1338 LocZnew
= locZ
* cosAng
+ locX
* sinAng
1347 # These two vertices will only be
1348 # added the very first time.
1349 if vertIdx
== 0 or vertIdx
== div
/ 2:
1350 verts
.append([locX
* radius
, locY
* radius
, locZ
])
1352 loopsJointsTemp
.append(loopJoint
)
1354 # Create complete loops (loopsJoints) out of the
1355 # double number of half loops in loopsJointsTemp
1356 for halfLoopIdx
in range(len(loopsJointsTemp
)):
1357 if (halfLoopIdx
== len(loopsJointsTemp
) - 1):
1362 idx2
= halfLoopIdx
+ 1
1365 loopJoint
.append(vertTemp2
)
1366 loopJoint
.extend(reversed(loopsJointsTemp
[idx2
]))
1367 loopJoint
.append(vertTemp1
)
1368 loopJoint
.extend(loopsJointsTemp
[idx1
])
1370 loopsJoints
.append(loopJoint
)
1372 # Create faces from the two
1373 # loop arrays (loopsJoints -> loopsEndCircles)
1374 for loopIdx
in range(len(loopsEndCircles
)):
1376 createFaces(loopsJoints
[loopIdx
],
1377 loopsEndCircles
[loopIdx
], closed
=True))
1379 if bpy
.context
.mode
== "OBJECT":
1380 if (context
.selected_objects
!= []) and context
.active_object
and \
1381 (context
.active_object
.data
is not None) and ('NJoint' in context
.active_object
.data
.keys()) and \
1382 (self
.change
== True):
1383 obj
= context
.active_object
1385 oldmeshname
= obj
.data
.name
1386 mesh
= create_mesh(context
, verts
, [], faces
, "N Joint")
1388 for material
in oldmesh
.materials
:
1389 obj
.data
.materials
.append(material
)
1390 bpy
.data
.meshes
.remove(oldmesh
)
1391 obj
.data
.name
= oldmeshname
1393 mesh
= create_mesh(context
, verts
, [], faces
, "N Joint")
1394 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
1396 obj
.data
["NJoint"] = True
1397 obj
.data
["change"] = False
1398 for prm
in NJointParameters():
1399 obj
.data
[prm
] = getattr(self
, prm
)
1401 if bpy
.context
.mode
== "EDIT_MESH":
1402 active_object
= context
.active_object
1403 name_active_object
= active_object
.name
1404 bpy
.ops
.object.mode_set(mode
='OBJECT')
1405 mesh
= create_mesh(context
, verts
, [], faces
, "TMP")
1406 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
1407 obj
.select_set(True)
1408 active_object
.select_set(True)
1409 bpy
.context
.view_layer
.objects
.active
= active_object
1410 bpy
.ops
.object.join()
1411 context
.active_object
.name
= name_active_object
1412 bpy
.ops
.object.mode_set(mode
='EDIT')
1414 if use_enter_edit_mode
:
1415 bpy
.ops
.object.mode_set(mode
= 'EDIT')
1417 # restore pre operator state
1418 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode