1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Author: Buerbaum Martin (Pontiac)
6 from math
import sin
, cos
, tan
, pi
, radians
7 from bpy
.types
import Operator
8 from bpy
.props
import (
14 from bpy_extras
import object_utils
16 # Create a new mesh (object) from verts/edges/faces.
17 # verts/edges/faces ... List of vertices/edges/faces for the
18 # new mesh (as used in from_pydata)
19 # name ... Name of the new mesh (& object)
21 def create_mesh(context
, verts
, edges
, faces
, name
):
23 mesh
= bpy
.data
.meshes
.new(name
)
25 # Make a mesh from a list of verts/edges/faces.
26 mesh
.from_pydata(verts
, edges
, faces
)
28 # Update mesh geometry after adding stuff.
33 # A very simple "bridge" tool.
35 def createFaces(vertIdx1
, vertIdx2
, closed
=False, flipped
=False):
38 if not vertIdx1
or not vertIdx2
:
41 if len(vertIdx1
) < 2 and len(vertIdx2
) < 2:
45 if (len(vertIdx1
) != len(vertIdx2
)):
46 if (len(vertIdx1
) == 1 and len(vertIdx2
) > 1):
54 # Bridge the start with the end.
61 face
.append(vertIdx1
[total
- 1])
65 face
= [vertIdx2
[0], vertIdx1
[0]]
67 face
.append(vertIdx1
[total
- 1])
68 face
.append(vertIdx2
[total
- 1])
71 # Bridge the rest of the faces.
72 for num
in range(total
- 1):
75 face
= [vertIdx2
[num
], vertIdx1
[0], vertIdx2
[num
+ 1]]
77 face
= [vertIdx2
[num
], vertIdx1
[num
],
78 vertIdx1
[num
+ 1], vertIdx2
[num
+ 1]]
82 face
= [vertIdx1
[0], vertIdx2
[num
], vertIdx2
[num
+ 1]]
84 face
= [vertIdx1
[num
], vertIdx2
[num
],
85 vertIdx2
[num
+ 1], vertIdx1
[num
+ 1]]
91 # Create the vertices and polygons for a simple elbow (bent pipe)
92 def ElbowJointParameters():
93 ElbowJointParameters
= [
100 return ElbowJointParameters
102 class AddElbowJoint(Operator
, object_utils
.AddObjectHelper
):
103 bl_idname
= "mesh.primitive_elbow_joint_add"
104 bl_label
= "Add Pipe Elbow"
105 bl_description
= "Construct an elbow pipe mesh"
106 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
108 ElbowJoint
: BoolProperty(name
= "ElbowJoint",
110 description
= "ElbowJoint")
112 #### change properties
113 change
: BoolProperty(name
= "Change",
115 description
= "change ElbowJoint")
117 radius
: FloatProperty(
119 description
="The radius of the pipe",
127 description
="Number of vertices (divisions)",
128 default
=32, min=3, max=256
130 angle
: FloatProperty(
132 description
="The angle of the branching pipe (i.e. the 'arm' - "
133 "Measured from the center line of the main pipe",
134 default
=radians(45.0),
139 startLength
: FloatProperty(
141 description
="Length of the beginning of the pipe",
147 endLength
: FloatProperty(
149 description
="Length of the end of the pipe",
156 def draw(self
, context
):
160 box
.prop(self
, 'radius')
161 box
.prop(self
, 'div')
162 box
.prop(self
, 'angle')
163 box
.prop(self
, 'startLength')
164 box
.prop(self
, 'endLength')
166 if self
.change
== False:
167 # generic transform props
169 box
.prop(self
, 'align', expand
=True)
170 box
.prop(self
, 'location', expand
=True)
171 box
.prop(self
, 'rotation', expand
=True)
173 def execute(self
, context
):
174 # turn off 'Enter Edit Mode'
175 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
176 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
183 startLength
= self
.startLength
184 endLength
= self
.endLength
189 loop1
= [] # The starting circle
190 loop2
= [] # The elbow circle
191 loop3
= [] # The end circle
193 # Create start circle
194 for vertIdx
in range(div
):
195 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
196 locX
= sin(curVertAngle
)
197 locY
= cos(curVertAngle
)
199 loop1
.append(len(verts
))
200 verts
.append([locX
* radius
, locY
* radius
, locZ
])
202 # Create deformed joint circle
203 for vertIdx
in range(div
):
204 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
205 locX
= sin(curVertAngle
)
206 locY
= cos(curVertAngle
)
207 locZ
= locX
* tan(angle
/ 2.0)
208 loop2
.append(len(verts
))
209 verts
.append([locX
* radius
, locY
* radius
, locZ
* radius
])
212 baseEndLocX
= -endLength
* sin(angle
)
213 baseEndLocZ
= endLength
* cos(angle
)
214 for vertIdx
in range(div
):
215 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
217 locX
= sin(curVertAngle
) * radius
218 locY
= cos(curVertAngle
) * radius
222 locZ
= locX
* cos(pi
/ 2.0 - angle
)
223 locX
= locX
* sin(pi
/ 2.0 - angle
)
225 loop3
.append(len(verts
))
226 # Translate and add circle vertices to the list.
227 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
230 faces
.extend(createFaces(loop1
, loop2
, closed
=True))
231 faces
.extend(createFaces(loop2
, loop3
, closed
=True))
233 if bpy
.context
.mode
== "OBJECT":
234 if (context
.selected_objects
!= []) and context
.active_object
and \
235 (context
.active_object
.data
is not None) and ('ElbowJoint' in context
.active_object
.data
.keys()) and \
236 (self
.change
== True):
237 obj
= context
.active_object
239 oldmeshname
= obj
.data
.name
240 mesh
= create_mesh(context
, verts
, [], faces
, "Elbow Joint")
242 for material
in oldmesh
.materials
:
243 obj
.data
.materials
.append(material
)
244 bpy
.data
.meshes
.remove(oldmesh
)
245 obj
.data
.name
= oldmeshname
247 mesh
= create_mesh(context
, verts
, [], faces
, "Elbow Joint")
248 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
252 obj
.data
["ElbowJoint"] = True
253 obj
.data
["change"] = False
254 for prm
in ElbowJointParameters():
255 obj
.data
[prm
] = getattr(self
, prm
)
257 if bpy
.context
.mode
== "EDIT_MESH":
258 active_object
= context
.active_object
259 name_active_object
= active_object
.name
260 bpy
.ops
.object.mode_set(mode
='OBJECT')
261 mesh
= create_mesh(context
, verts
, [], faces
, "TMP")
262 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
264 active_object
.select_set(True)
265 bpy
.context
.view_layer
.objects
.active
= active_object
266 bpy
.ops
.object.join()
267 context
.active_object
.name
= name_active_object
268 bpy
.ops
.object.mode_set(mode
='EDIT')
270 if use_enter_edit_mode
:
271 bpy
.ops
.object.mode_set(mode
= 'EDIT')
273 # restore pre operator state
274 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode
279 # Create the vertices and polygons for a simple tee (T) joint
280 # The base arm of the T can be positioned in an angle if needed though
281 def TeeJointParameters():
282 TeeJointParameters
= [
290 return TeeJointParameters
292 class AddTeeJoint(Operator
, object_utils
.AddObjectHelper
):
293 bl_idname
= "mesh.primitive_tee_joint_add"
294 bl_label
= "Add Pipe Tee-Joint"
295 bl_description
= "Construct a tee-joint pipe mesh"
296 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
298 TeeJoint
: BoolProperty(name
= "TeeJoint",
300 description
= "TeeJoint")
302 #### change properties
303 change
: BoolProperty(name
= "Change",
305 description
= "change TeeJoint")
307 radius
: FloatProperty(
309 description
="The radius of the pipe",
317 description
="Number of vertices (divisions)",
322 angle
: FloatProperty(
324 description
="The angle of the branching pipe (i.e. the 'arm' - "
325 "Measured from the center line of the main pipe",
326 default
=radians(90.0),
331 startLength
: FloatProperty(
333 description
="Length of the beginning of the"
334 " main pipe (the straight one)",
340 endLength
: FloatProperty(
342 description
="Length of the end of the"
343 " main pipe (the straight one)",
349 branchLength
: FloatProperty(
351 description
="Length of the arm pipe (the bent one)",
358 def draw(self
, context
):
362 box
.prop(self
, 'radius')
363 box
.prop(self
, 'div')
364 box
.prop(self
, 'angle')
365 box
.prop(self
, 'startLength')
366 box
.prop(self
, 'endLength')
367 box
.prop(self
, 'branchLength')
369 if self
.change
== False:
370 # generic transform props
372 box
.prop(self
, 'align', expand
=True)
373 box
.prop(self
, 'location', expand
=True)
374 box
.prop(self
, 'rotation', expand
=True)
376 def execute(self
, context
):
377 # turn off 'Enter Edit Mode'
378 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
379 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
386 startLength
= self
.startLength
387 endLength
= self
.endLength
388 branchLength
= self
.branchLength
391 # Odd vertice number not supported (yet)
392 self
.report({'INFO'}, "Odd vertices number is not yet supported")
398 # List of vert indices of each cross section
399 loopMainStart
= [] # Vert indices for the beginning of the main pipe
400 loopJoint1
= [] # Vert indices for joint that is used to connect the joint & loopMainStart
401 loopJoint2
= [] # Vert indices for joint that is used to connect the joint & loopArm
402 loopJoint3
= [] # Vert index for joint that is used to connect the joint & loopMainEnd
403 loopArm
= [] # Vert indices for the end of the arm
404 loopMainEnd
= [] # Vert indices for the end of the main pipe.
406 # Create start circle (main pipe)
407 for vertIdx
in range(div
):
408 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
409 locX
= sin(curVertAngle
)
410 locY
= cos(curVertAngle
)
412 loopMainStart
.append(len(verts
))
413 verts
.append([locX
* radius
, locY
* radius
, locZ
])
415 # Create deformed joint circle
418 for vertIdx
in range(div
):
419 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
420 locX
= sin(curVertAngle
)
421 locY
= cos(curVertAngle
)
424 vertTemp1
= len(verts
)
425 if vertIdx
== div
/ 2:
426 # @todo: This will possibly break if we
427 # ever support odd divisions.
428 vertTemp2
= len(verts
)
430 loopJoint1
.append(len(verts
))
431 if (vertIdx
< div
/ 2):
432 # Straight side of main pipe.
434 loopJoint3
.append(len(verts
))
437 locZ
= locX
* tan(angle
/ 2.0)
438 loopJoint2
.append(len(verts
))
440 verts
.append([locX
* radius
, locY
* radius
, locZ
* radius
])
442 # Create 2. deformed joint (half-)circle
444 for vertIdx
in range(div
):
445 if (vertIdx
> div
/ 2):
446 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
447 locX
= sin(curVertAngle
)
448 locY
= -cos(curVertAngle
)
449 locZ
= -(radius
* locX
* tan((pi
- angle
) / 2.0))
450 loopTemp
.append(len(verts
))
451 verts
.append([locX
* radius
, locY
* radius
, locZ
])
453 loopTemp2
= loopTemp
[:]
457 loopTemp
.append(vertTemp1
)
459 loopJoint2
.extend(loopTemp
)
463 loopTemp2
.append(vertTemp2
)
465 loopJoint3
.extend(loopTemp2
)
467 # Create end circle (branching pipe)
468 baseEndLocX
= -branchLength
* sin(angle
)
469 baseEndLocZ
= branchLength
* cos(angle
)
470 for vertIdx
in range(div
):
471 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
473 locX
= sin(curVertAngle
) * radius
474 locY
= cos(curVertAngle
) * radius
478 locZ
= locX
* cos(pi
/ 2.0 - angle
)
479 locX
= locX
* sin(pi
/ 2.0 - angle
)
481 loopArm
.append(len(verts
))
483 # Add translated circle.
484 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
486 # Create end circle (main pipe)
487 for vertIdx
in range(div
):
488 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
489 locX
= sin(curVertAngle
)
490 locY
= cos(curVertAngle
)
492 loopMainEnd
.append(len(verts
))
493 verts
.append([locX
* radius
, locY
* radius
, locZ
])
496 faces
.extend(createFaces(loopMainStart
, loopJoint1
, closed
=True))
497 faces
.extend(createFaces(loopJoint2
, loopArm
, closed
=True))
498 faces
.extend(createFaces(loopJoint3
, loopMainEnd
, closed
=True))
500 if bpy
.context
.mode
== "OBJECT":
501 if (context
.selected_objects
!= []) and context
.active_object
and \
502 (context
.active_object
.data
is not None) and ('TeeJoint' in context
.active_object
.data
.keys()) and \
503 (self
.change
== True):
504 obj
= context
.active_object
506 oldmeshname
= obj
.data
.name
507 mesh
= create_mesh(context
, verts
, [], faces
, "Tee Joint")
509 for material
in oldmesh
.materials
:
510 obj
.data
.materials
.append(material
)
511 bpy
.data
.meshes
.remove(oldmesh
)
512 obj
.data
.name
= oldmeshname
514 mesh
= create_mesh(context
, verts
, [], faces
, "Tee Joint")
515 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
519 obj
.data
["TeeJoint"] = True
520 obj
.data
["change"] = False
521 for prm
in TeeJointParameters():
522 obj
.data
[prm
] = getattr(self
, prm
)
524 if bpy
.context
.mode
== "EDIT_MESH":
525 active_object
= context
.active_object
526 name_active_object
= active_object
.name
527 bpy
.ops
.object.mode_set(mode
='OBJECT')
528 mesh
= create_mesh(context
, verts
, [], faces
, "TMP")
529 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
531 active_object
.select_set(True)
532 bpy
.context
.view_layer
.objects
.active
= active_object
533 bpy
.ops
.object.join()
534 context
.active_object
.name
= name_active_object
535 bpy
.ops
.object.mode_set(mode
='EDIT')
537 if use_enter_edit_mode
:
538 bpy
.ops
.object.mode_set(mode
= 'EDIT')
540 # restore pre operator state
541 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode
545 def WyeJointParameters():
546 WyeJointParameters
= [
555 return WyeJointParameters
557 class AddWyeJoint(Operator
, object_utils
.AddObjectHelper
):
558 bl_idname
= "mesh.primitive_wye_joint_add"
559 bl_label
= "Add Pipe Wye-Joint"
560 bl_description
= "Construct a wye-joint pipe mesh"
561 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
563 WyeJoint
: BoolProperty(name
= "WyeJoint",
565 description
= "WyeJoint")
567 #### change properties
568 change
: BoolProperty(name
= "Change",
570 description
= "change WyeJoint")
572 radius
: FloatProperty(
574 description
="The radius of the pipe",
582 description
="Number of vertices (divisions)",
587 angle1
: FloatProperty(
589 description
="The angle of the 1. branching pipe "
590 "(measured from the center line of the main pipe)",
591 default
=radians(45.0),
596 angle2
: FloatProperty(
598 description
="The angle of the 2. branching pipe "
599 "(measured from the center line of the main pipe) ",
600 default
=radians(45.0),
605 startLength
: FloatProperty(
607 description
="Length of the beginning of the"
608 " main pipe (the straight one)",
614 branch1Length
: FloatProperty(
616 description
="Length of the 1. arm",
622 branch2Length
: FloatProperty(
624 description
="Length of the 2. arm",
631 def draw(self
, context
):
635 box
.prop(self
, 'radius')
636 box
.prop(self
, 'div')
637 box
.prop(self
, 'angle1')
638 box
.prop(self
, 'angle2')
639 box
.prop(self
, 'startLength')
640 box
.prop(self
, 'branch1Length')
641 box
.prop(self
, 'branch2Length')
643 if self
.change
== False:
644 # generic transform props
646 box
.prop(self
, 'align', expand
=True)
647 box
.prop(self
, 'location', expand
=True)
648 box
.prop(self
, 'rotation', expand
=True)
650 def execute(self
, context
):
651 # turn off 'Enter Edit Mode'
652 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
653 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
661 startLength
= self
.startLength
662 branch1Length
= self
.branch1Length
663 branch2Length
= self
.branch2Length
666 # Odd vertice number not supported (yet)
667 self
.report({'INFO'}, "Odd vertices number is not yet supported")
673 # List of vert indices of each cross section
674 loopMainStart
= [] # Vert indices for the beginning of the main pipe
675 loopJoint1
= [] # Vert index for joint that is used to connect the joint & loopMainStart
676 loopJoint2
= [] # Vert index for joint that is used to connect the joint & loopArm1
677 loopJoint3
= [] # Vert index for joint that is used to connect the joint & loopArm2
678 loopArm1
= [] # Vert idxs for end of the 1. arm
679 loopArm2
= [] # Vert idxs for end of the 2. arm
681 # Create start circle
682 for vertIdx
in range(div
):
683 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
684 locX
= sin(curVertAngle
)
685 locY
= cos(curVertAngle
)
687 loopMainStart
.append(len(verts
))
688 verts
.append([locX
* radius
, locY
* radius
, locZ
])
690 # Create deformed joint circle
693 for vertIdx
in range(div
):
694 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
695 locX
= sin(curVertAngle
)
696 locY
= cos(curVertAngle
)
699 vertTemp2
= len(verts
)
700 if vertIdx
== div
/ 2:
701 # @todo: This will possibly break if we
702 # ever support odd divisions.
703 vertTemp1
= len(verts
)
705 loopJoint1
.append(len(verts
))
706 if (vertIdx
> div
/ 2):
707 locZ
= locX
* tan(angle1
/ 2.0)
708 loopJoint2
.append(len(verts
))
710 locZ
= locX
* tan(-angle2
/ 2.0)
711 loopJoint3
.append(len(verts
))
713 verts
.append([locX
* radius
, locY
* radius
, locZ
* radius
])
715 # Create 2. deformed joint (half-)circle
717 angleJoint
= (angle2
- angle1
) / 2.0
718 for vertIdx
in range(div
):
719 if (vertIdx
> div
/ 2):
720 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
722 locX
= (-sin(curVertAngle
) * sin(angleJoint
) / sin(angle2
- angleJoint
))
723 locY
= -cos(curVertAngle
)
724 locZ
= (-(sin(curVertAngle
) * cos(angleJoint
) / sin(angle2
- angleJoint
)))
726 loopTemp
.append(len(verts
))
727 verts
.append([locX
* radius
, locY
* radius
, locZ
* radius
])
729 loopTemp2
= loopTemp
[:]
732 loopTemp
.append(vertTemp1
)
734 loopTemp
.append(vertTemp2
)
736 loopJoint2
.extend(loopTemp
)
741 loopJoint3
.extend(loopTemp2
)
743 # Create end circle (1. branching pipe)
744 baseEndLocX
= -branch1Length
* sin(angle1
)
745 baseEndLocZ
= branch1Length
* cos(angle1
)
746 for vertIdx
in range(div
):
747 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
749 locX
= sin(curVertAngle
) * radius
750 locY
= cos(curVertAngle
) * radius
754 locZ
= locX
* cos(pi
/ 2.0 - angle1
)
755 locX
= locX
* sin(pi
/ 2.0 - angle1
)
757 loopArm1
.append(len(verts
))
758 # Add translated circle.
759 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
761 # Create end circle (2. branching pipe)
762 baseEndLocX
= branch2Length
* sin(angle2
)
763 baseEndLocZ
= branch2Length
* cos(angle2
)
764 for vertIdx
in range(div
):
765 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
767 locX
= sin(curVertAngle
) * radius
768 locY
= cos(curVertAngle
) * radius
772 locZ
= locX
* cos(pi
/ 2.0 + angle2
)
773 locX
= locX
* sin(pi
/ 2.0 + angle2
)
775 loopArm2
.append(len(verts
))
776 # Add translated circle
777 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
780 faces
.extend(createFaces(loopMainStart
, loopJoint1
, closed
=True))
781 faces
.extend(createFaces(loopJoint2
, loopArm1
, closed
=True))
782 faces
.extend(createFaces(loopJoint3
, loopArm2
, closed
=True))
784 if bpy
.context
.mode
== "OBJECT":
785 if (context
.selected_objects
!= []) and context
.active_object
and \
786 (context
.active_object
.data
is not None) and ('WyeJoint' in context
.active_object
.data
.keys()) and \
787 (self
.change
== True):
788 obj
= context
.active_object
790 oldmeshname
= obj
.data
.name
791 mesh
= create_mesh(context
, verts
, [], faces
, "Wye Joint")
793 for material
in oldmesh
.materials
:
794 obj
.data
.materials
.append(material
)
795 bpy
.data
.meshes
.remove(oldmesh
)
796 obj
.data
.name
= oldmeshname
798 mesh
= create_mesh(context
, verts
, [], faces
, "Wye Joint")
799 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
803 obj
.data
["WyeJoint"] = True
804 obj
.data
["change"] = False
805 for prm
in WyeJointParameters():
806 obj
.data
[prm
] = getattr(self
, prm
)
808 if bpy
.context
.mode
== "EDIT_MESH":
809 active_object
= context
.active_object
810 name_active_object
= active_object
.name
811 bpy
.ops
.object.mode_set(mode
='OBJECT')
812 mesh
= create_mesh(context
, verts
, [], faces
, "TMP")
813 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
815 active_object
.select_set(True)
816 bpy
.context
.view_layer
.objects
.active
= active_object
817 bpy
.ops
.object.join()
818 context
.active_object
.name
= name_active_object
819 bpy
.ops
.object.mode_set(mode
='EDIT')
821 if use_enter_edit_mode
:
822 bpy
.ops
.object.mode_set(mode
= 'EDIT')
824 # restore pre operator state
825 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode
830 # Create the vertices and polygons for a cross (+ or X) pipe joint
831 def CrossJointParameters():
832 CrossJointParameters
= [
843 return CrossJointParameters
845 class AddCrossJoint(Operator
, object_utils
.AddObjectHelper
):
846 bl_idname
= "mesh.primitive_cross_joint_add"
847 bl_label
= "Add Pipe Cross-Joint"
848 bl_description
= "Construct a cross-joint pipe mesh"
849 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
851 CrossJoint
: BoolProperty(name
= "CrossJoint",
853 description
= "CrossJoint")
855 #### change properties
856 change
: BoolProperty(name
= "Change",
858 description
= "change CrossJoint")
860 radius
: FloatProperty(
862 description
="The radius of the pipe",
870 description
="Number of vertices (divisions)",
875 angle1
: FloatProperty(
877 description
="The angle of the 1. arm (from the main axis)",
878 default
=radians(90.0),
883 angle2
: FloatProperty(name
="Angle 2",
884 description
="The angle of the 2. arm (from the main axis)",
885 default
=radians(90.0),
890 angle3
: FloatProperty(name
="Angle 3 (center)",
891 description
="The angle of the center arm (from the main axis)",
892 default
=radians(0.0),
897 startLength
: FloatProperty(
899 description
="Length of the beginning of the "
900 "main pipe (the straight one)",
906 branch1Length
: FloatProperty(name
="Length Arm 1",
907 description
="Length of the 1. arm",
913 branch2Length
: FloatProperty(
915 description
="Length of the 2. arm",
921 branch3Length
: FloatProperty(
922 name
="Length Arm 3 (center)",
923 description
="Length of the center arm",
930 def draw(self
, context
):
934 box
.prop(self
, 'radius')
935 box
.prop(self
, 'div')
936 box
.prop(self
, 'angle1')
937 box
.prop(self
, 'angle2')
938 box
.prop(self
, 'angle3')
939 box
.prop(self
, 'startLength')
940 box
.prop(self
, 'branch1Length')
941 box
.prop(self
, 'branch2Length')
942 box
.prop(self
, 'branch3Length')
944 if self
.change
== False:
945 # generic transform props
947 box
.prop(self
, 'align', expand
=True)
948 box
.prop(self
, 'location', expand
=True)
949 box
.prop(self
, 'rotation', expand
=True)
951 def execute(self
, context
):
952 # turn off 'Enter Edit Mode'
953 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
954 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
963 startLength
= self
.startLength
964 branch1Length
= self
.branch1Length
965 branch2Length
= self
.branch2Length
966 branch3Length
= self
.branch3Length
968 # Odd vertice number not supported (yet)
969 self
.report({'INFO'}, "Odd vertices number is not yet supported")
975 # List of vert indices of each cross section
976 loopMainStart
= [] # Vert indices for the beginning of the main pipe
977 loopJoint1
= [] # Vert index for joint that is used to connect the joint & loopMainStart
978 loopJoint2
= [] # Vert index for joint that is used to connect the joint & loopArm1
979 loopJoint3
= [] # Vert index for joint that is used to connect the joint & loopArm2
980 loopJoint4
= [] # Vert index for joint that is used to connect the joint & loopArm3
981 loopArm1
= [] # Vert idxs for the end of the 1. arm
982 loopArm2
= [] # Vert idxs for the end of the 2. arm
983 loopArm3
= [] # Vert idxs for the center arm end
985 # Create start circle
986 for vertIdx
in range(div
):
987 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
988 locX
= sin(curVertAngle
)
989 locY
= cos(curVertAngle
)
991 loopMainStart
.append(len(verts
))
992 verts
.append([locX
* radius
, locY
* radius
, locZ
])
994 # Create 1. deformed joint circle
997 for vertIdx
in range(div
):
998 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
999 locX
= sin(curVertAngle
)
1000 locY
= cos(curVertAngle
)
1003 vertTemp2
= len(verts
)
1004 if vertIdx
== div
/ 2:
1005 # @todo: This will possibly break if we
1006 # ever support odd divisions.
1007 vertTemp1
= len(verts
)
1009 loopJoint1
.append(len(verts
))
1010 if (vertIdx
> div
/ 2):
1011 locZ
= locX
* tan(angle1
/ 2.0)
1012 loopJoint2
.append(len(verts
))
1014 locZ
= locX
* tan(-angle2
/ 2.0)
1015 loopJoint3
.append(len(verts
))
1017 verts
.append([locX
* radius
, locY
* radius
, locZ
* radius
])
1019 # Create 2. deformed joint circle
1022 angleJoint1
= (angle1
- angle3
) / 2.0
1023 angleJoint2
= (angle2
+ angle3
) / 2.0
1024 for vertIdx
in range(div
):
1025 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
1027 # Skip pole vertices
1028 # @todo: This will possibly break if
1029 # we ever support odd divisions
1030 if not (vertIdx
== 0) and not (vertIdx
== div
/ 2):
1032 if (vertIdx
> div
/ 2):
1033 angleJoint
= angleJoint1
1036 loopTempA
.append(len(verts
))
1039 angleJoint
= angleJoint2
1042 loopTempB
.append(len(verts
))
1044 locX
= (sin(curVertAngle
) * sin(angleJoint
) / sin(angle
- angleJoint
))
1045 locY
= -cos(curVertAngle
)
1046 locZ
= (Z
* (sin(curVertAngle
) * cos(angleJoint
) / sin(angle
- angleJoint
)))
1048 verts
.append([locX
* radius
, locY
* radius
, locZ
* radius
])
1050 loopTempA2
= loopTempA
[:]
1051 loopTempB2
= loopTempB
[:]
1052 loopTempB3
= loopTempB
[:]
1055 loopTempA
.append(vertTemp1
)
1057 loopTempA
.append(vertTemp2
)
1058 loopJoint2
.reverse()
1059 loopJoint2
.extend(loopTempA
)
1060 loopJoint2
.reverse()
1063 loopJoint3
.extend(loopTempB3
)
1066 loopTempA2
.append(vertTemp1
)
1067 loopTempA2
.reverse()
1068 loopTempB2
.append(vertTemp2
)
1069 loopJoint4
.extend(reversed(loopTempB2
))
1070 loopJoint4
.extend(loopTempA2
)
1072 # Create end circle (1. branching pipe)
1073 baseEndLocX
= -branch1Length
* sin(angle1
)
1074 baseEndLocZ
= branch1Length
* cos(angle1
)
1075 for vertIdx
in range(div
):
1076 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
1078 locX
= sin(curVertAngle
) * radius
1079 locY
= cos(curVertAngle
) * radius
1083 locZ
= locX
* cos(pi
/ 2.0 - angle1
)
1084 locX
= locX
* sin(pi
/ 2.0 - angle1
)
1086 loopArm1
.append(len(verts
))
1087 # Add translated circle.
1088 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
1090 # Create end circle (2. branching pipe)
1091 baseEndLocX
= branch2Length
* sin(angle2
)
1092 baseEndLocZ
= branch2Length
* cos(angle2
)
1093 for vertIdx
in range(div
):
1094 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
1096 locX
= sin(curVertAngle
) * radius
1097 locY
= cos(curVertAngle
) * radius
1101 locZ
= locX
* cos(pi
/ 2.0 + angle2
)
1102 locX
= locX
* sin(pi
/ 2.0 + angle2
)
1104 loopArm2
.append(len(verts
))
1105 # Add translated circle
1106 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
1108 # Create end circle (center pipe)
1109 baseEndLocX
= branch3Length
* sin(angle3
)
1110 baseEndLocZ
= branch3Length
* cos(angle3
)
1111 for vertIdx
in range(div
):
1112 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
1114 locX
= sin(curVertAngle
) * radius
1115 locY
= cos(curVertAngle
) * radius
1119 locZ
= locX
* cos(pi
/ 2.0 + angle3
)
1120 locX
= locX
* sin(pi
/ 2.0 + angle3
)
1122 loopArm3
.append(len(verts
))
1123 # Add translated circle
1124 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
1127 faces
.extend(createFaces(loopMainStart
, loopJoint1
, closed
=True))
1128 faces
.extend(createFaces(loopJoint2
, loopArm1
, closed
=True))
1129 faces
.extend(createFaces(loopJoint3
, loopArm2
, closed
=True))
1130 faces
.extend(createFaces(loopJoint4
, loopArm3
, closed
=True))
1132 if bpy
.context
.mode
== "OBJECT":
1133 if (context
.selected_objects
!= []) and context
.active_object
and \
1134 (context
.active_object
.data
is not None) and ('CrossJoint' in context
.active_object
.data
.keys()) and \
1135 (self
.change
== True):
1136 obj
= context
.active_object
1138 oldmeshname
= obj
.data
.name
1139 mesh
= create_mesh(context
, verts
, [], faces
, "Cross Joint")
1141 for material
in oldmesh
.materials
:
1142 obj
.data
.materials
.append(material
)
1143 bpy
.data
.meshes
.remove(oldmesh
)
1144 obj
.data
.name
= oldmeshname
1146 mesh
= create_mesh(context
, verts
, [], faces
, "Cross Joint")
1147 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
1151 obj
.data
["CrossJoint"] = True
1152 obj
.data
["change"] = False
1153 for prm
in CrossJointParameters():
1154 obj
.data
[prm
] = getattr(self
, prm
)
1156 if bpy
.context
.mode
== "EDIT_MESH":
1157 active_object
= context
.active_object
1158 name_active_object
= active_object
.name
1159 bpy
.ops
.object.mode_set(mode
='OBJECT')
1160 mesh
= create_mesh(context
, verts
, [], faces
, "TMP")
1161 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
1162 obj
.select_set(True)
1163 active_object
.select_set(True)
1164 bpy
.context
.view_layer
.objects
.active
= active_object
1165 bpy
.ops
.object.join()
1166 context
.active_object
.name
= name_active_object
1167 bpy
.ops
.object.mode_set(mode
='EDIT')
1169 if use_enter_edit_mode
:
1170 bpy
.ops
.object.mode_set(mode
= 'EDIT')
1172 # restore pre operator state
1173 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode
1178 # Create the vertices and polygons for a regular n-joint
1179 def NJointParameters():
1180 NJointParameters
= [
1186 return NJointParameters
1188 class AddNJoint(Operator
, object_utils
.AddObjectHelper
):
1189 bl_idname
= "mesh.primitive_n_joint_add"
1190 bl_label
= "Add Pipe N-Joint"
1191 bl_description
= "Construct a n-joint pipe mesh"
1192 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
1194 NJoint
: BoolProperty(name
= "NJoint",
1196 description
= "NJoint")
1198 #### change properties
1199 change
: BoolProperty(name
= "Change",
1201 description
= "change NJoint")
1203 radius
: FloatProperty(
1205 description
="The radius of the pipe",
1213 description
="Number of vertices (divisions)",
1218 number
: IntProperty(
1219 name
="Arms / Joints",
1220 description
="Number of joints / arms",
1225 length
: FloatProperty(
1227 description
="Length of each joint / arm",
1234 def draw(self
, context
):
1235 layout
= self
.layout
1238 box
.prop(self
, 'radius')
1239 box
.prop(self
, 'div')
1240 box
.prop(self
, 'number')
1241 box
.prop(self
, 'length')
1243 if self
.change
== False:
1244 # generic transform props
1246 box
.prop(self
, 'align', expand
=True)
1247 box
.prop(self
, 'location', expand
=True)
1248 box
.prop(self
, 'rotation', expand
=True)
1250 def execute(self
, context
):
1251 # turn off 'Enter Edit Mode'
1252 use_enter_edit_mode
= bpy
.context
.preferences
.edit
.use_enter_edit_mode
1253 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= False
1255 radius
= self
.radius
1257 number
= self
.number
1258 length
= self
.length
1261 # Odd vertice number not supported (yet)
1262 self
.report({'INFO'}, "Odd vertices number is not yet supported")
1263 return {'CANCELLED'}
1266 return {'CANCELLED'}
1271 loopsEndCircles
= []
1272 loopsJointsTemp
= []
1278 angleDiv
= (2.0 * pi
/ number
)
1280 # Create vertices for the end circles
1281 for num
in range(number
):
1283 # Create start circle
1284 angle
= num
* angleDiv
1286 baseEndLocX
= length
* sin(angle
)
1287 baseEndLocZ
= length
* cos(angle
)
1288 for vertIdx
in range(div
):
1289 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
1291 locX
= sin(curVertAngle
) * radius
1292 locY
= cos(curVertAngle
) * radius
1296 locZ
= locX
* cos(pi
/ 2.0 + angle
)
1297 locX
= locX
* sin(pi
/ 2.0 + angle
)
1299 circle
.append(len(verts
))
1300 # Add translated circle
1301 verts
.append([baseEndLocX
+ locX
, locY
, baseEndLocZ
+ locZ
])
1303 loopsEndCircles
.append(circle
)
1305 # Create vertices for the joint circles
1307 for vertIdx
in range(div
):
1308 curVertAngle
= vertIdx
* (2.0 * pi
/ div
)
1309 locX
= sin(curVertAngle
)
1310 locY
= cos(curVertAngle
)
1313 # Store pole vertices
1316 vertTemp2
= len(verts
)
1319 elif vertIdx
== div
/ 2:
1320 # @todo: This will possibly break if we
1321 # ever support odd divisions
1323 vertTemp1
= len(verts
)
1328 if (vertIdx
> div
/ 2):
1329 locZ
= -locX
* tan((pi
- angleDiv
) / 2.0)
1330 loopJoint
.append(len(verts
))
1333 cosAng
= cos(-angle
)
1334 sinAng
= sin(-angle
)
1335 LocXnew
= locX
* cosAng
- locZ
* sinAng
1336 LocZnew
= locZ
* cosAng
+ locX
* sinAng
1345 # These two vertices will only be
1346 # added the very first time.
1347 if vertIdx
== 0 or vertIdx
== div
/ 2:
1348 verts
.append([locX
* radius
, locY
* radius
, locZ
])
1350 loopsJointsTemp
.append(loopJoint
)
1352 # Create complete loops (loopsJoints) out of the
1353 # double number of half loops in loopsJointsTemp
1354 for halfLoopIdx
in range(len(loopsJointsTemp
)):
1355 if (halfLoopIdx
== len(loopsJointsTemp
) - 1):
1360 idx2
= halfLoopIdx
+ 1
1363 loopJoint
.append(vertTemp2
)
1364 loopJoint
.extend(reversed(loopsJointsTemp
[idx2
]))
1365 loopJoint
.append(vertTemp1
)
1366 loopJoint
.extend(loopsJointsTemp
[idx1
])
1368 loopsJoints
.append(loopJoint
)
1370 # Create faces from the two
1371 # loop arrays (loopsJoints -> loopsEndCircles)
1372 for loopIdx
in range(len(loopsEndCircles
)):
1374 createFaces(loopsJoints
[loopIdx
],
1375 loopsEndCircles
[loopIdx
], closed
=True))
1377 if bpy
.context
.mode
== "OBJECT":
1378 if (context
.selected_objects
!= []) and context
.active_object
and \
1379 (context
.active_object
.data
is not None) and ('NJoint' in context
.active_object
.data
.keys()) and \
1380 (self
.change
== True):
1381 obj
= context
.active_object
1383 oldmeshname
= obj
.data
.name
1384 mesh
= create_mesh(context
, verts
, [], faces
, "N Joint")
1386 for material
in oldmesh
.materials
:
1387 obj
.data
.materials
.append(material
)
1388 bpy
.data
.meshes
.remove(oldmesh
)
1389 obj
.data
.name
= oldmeshname
1391 mesh
= create_mesh(context
, verts
, [], faces
, "N Joint")
1392 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
1394 obj
.data
["NJoint"] = True
1395 obj
.data
["change"] = False
1396 for prm
in NJointParameters():
1397 obj
.data
[prm
] = getattr(self
, prm
)
1399 if bpy
.context
.mode
== "EDIT_MESH":
1400 active_object
= context
.active_object
1401 name_active_object
= active_object
.name
1402 bpy
.ops
.object.mode_set(mode
='OBJECT')
1403 mesh
= create_mesh(context
, verts
, [], faces
, "TMP")
1404 obj
= object_utils
.object_data_add(context
, mesh
, operator
=self
)
1405 obj
.select_set(True)
1406 active_object
.select_set(True)
1407 bpy
.context
.view_layer
.objects
.active
= active_object
1408 bpy
.ops
.object.join()
1409 context
.active_object
.name
= name_active_object
1410 bpy
.ops
.object.mode_set(mode
='EDIT')
1412 if use_enter_edit_mode
:
1413 bpy
.ops
.object.mode_set(mode
= 'EDIT')
1415 # restore pre operator state
1416 bpy
.context
.preferences
.edit
.use_enter_edit_mode
= use_enter_edit_mode