Fix: Node Wrangler: error when previewing without Geo output socket
[blender-addons.git] / add_curve_sapling / __init__.py
blob07607b77b4f009aa370e912cdb5b5946a3e984cc
1 # SPDX-FileCopyrightText: 2011-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 bl_info = {
6 "name": "Sapling Tree Gen",
7 "author": "Andrew Hale (TrumanBlending), Aaron Buchler, CansecoGPC",
8 "version": (0, 3, 5),
9 "blender": (2, 80, 0),
10 "location": "View3D > Add > Curve",
11 "description": ("Adds a parametric tree. The method is presented by "
12 "Jason Weber & Joseph Penn in their paper 'Creation and Rendering of "
13 "Realistic Trees'"),
14 "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/sapling.html",
15 "category": "Add Curve",
18 if "bpy" in locals():
19 import importlib
20 importlib.reload(utils)
21 else:
22 from add_curve_sapling import utils
24 import bpy
25 import time
26 import os
27 import ast
29 # import cProfile
31 from bpy.types import (
32 Operator,
33 Menu,
35 from bpy.props import (
36 BoolProperty,
37 EnumProperty,
38 FloatProperty,
39 FloatVectorProperty,
40 IntProperty,
41 IntVectorProperty,
42 StringProperty,
45 useSet = False
47 shapeList = [('0', 'Conical (0)', 'Shape = 0'),
48 ('1', 'Spherical (1)', 'Shape = 1'),
49 ('2', 'Hemispherical (2)', 'Shape = 2'),
50 ('3', 'Cylindrical (3)', 'Shape = 3'),
51 ('4', 'Tapered Cylindrical (4)', 'Shape = 4'),
52 ('5', 'Flame (5)', 'Shape = 5'),
53 ('6', 'Inverse Conical (6)', 'Shape = 6'),
54 ('7', 'Tend Flame (7)', 'Shape = 7')]
56 shapeList3 = [('0', 'Conical', ''),
57 ('6', 'Inverse Conical', ''),
58 ('1', 'Spherical', ''),
59 ('2', 'Hemispherical', ''),
60 ('3', 'Cylindrical', ''),
61 ('4', 'Tapered Cylindrical', ''),
62 ('10', 'Inverse Tapered Cylindrical', ''),
63 ('5', 'Flame', ''),
64 ('7', 'Tend Flame', ''),
65 ('8', 'Custom Shape', '')]
67 shapeList4 = [('0', 'Conical', ''),
68 ('6', 'Inverse Conical', ''),
69 ('1', 'Spherical', ''),
70 ('2', 'Hemispherical', ''),
71 ('3', 'Cylindrical', ''),
72 ('4', 'Tapered Cylindrical', ''),
73 ('10', 'Inverse Tapered Cylindrical', ''),
74 ('5', 'Flame', ''),
75 ('7', 'Tend Flame', '')]
77 handleList = [('0', 'Auto', 'Auto'),
78 ('1', 'Vector', 'Vector')]
80 settings = [('0', 'Geometry', 'Geometry'),
81 ('1', 'Branch Radius', 'Branch Radius'),
82 ('2', 'Branch Splitting', 'Branch Splitting'),
83 ('3', 'Branch Growth', 'Branch Growth'),
84 ('4', 'Pruning', 'Pruning'),
85 ('5', 'Leaves', 'Leaves'),
86 ('6', 'Armature', 'Armature'),
87 ('7', 'Animation', 'Animation')]
89 branchmodes = [("original", "Original", "rotate around each branch"),
90 ("rotate", "Rotate", "evenly distribute branches to point outward from center of tree"),
91 ("random", "Random", "choose random point")]
94 def getPresetpath():
95 """Support user defined scripts directory
96 Find the first occurrence of add_curve_sapling/presets in possible script paths
97 and return it as preset path"""
99 script_file = os.path.realpath(__file__)
100 directory = os.path.dirname(script_file)
101 directory = os.path.join(directory, "presets")
102 return directory
105 def getPresetpaths():
106 """Return paths for both local and user preset folders"""
107 userDir = os.path.join(bpy.utils.script_path_user(), 'presets', 'operator', 'add_curve_sapling')
109 if os.path.isdir(userDir):
110 pass
111 else:
112 os.makedirs(userDir)
114 script_file = os.path.realpath(__file__)
115 directory = os.path.dirname(script_file)
116 localDir = os.path.join(directory, "presets")
118 return (localDir, userDir)
121 class ExportData(Operator):
122 """This operator handles writing presets to file"""
123 bl_idname = 'sapling.exportdata'
124 bl_label = 'Export Preset'
126 data: StringProperty()
128 def execute(self, context):
129 # Unpack some data from the input
130 data, filename, overwrite = eval(self.data)
132 try:
133 # Check whether the file exists by trying to open it.
134 f = open(os.path.join(getPresetpaths()[1], filename + '.py'), 'r')
135 f.close()
136 # If it exists then report an error
137 self.report({'ERROR_INVALID_INPUT'}, 'Preset Already Exists')
138 return {'CANCELLED'}
139 except IOError:
140 if data:
141 # If it doesn't exist, create the file with the required data
142 f = open(os.path.join(getPresetpaths()[1], filename + '.py'), 'w')
143 f.write(data)
144 f.close()
145 return {'FINISHED'}
146 else:
147 return {'CANCELLED'}
149 fpath1 = os.path.join(getPresetpaths()[0], filename + '.py')
150 fpath2 = os.path.join(getPresetpaths()[1], filename + '.py')
152 if os.path.exists(fpath1):
153 # If it exists in built-in presets then report an error
154 self.report({'ERROR_INVALID_INPUT'}, 'Can\'t have same name as built-in preset')
155 return {'CANCELLED'}
156 elif (not os.path.exists(fpath2)) or (os.path.exists(fpath2) and overwrite):
157 # if (it does not exist) or (exists and overwrite) then write file
158 if data:
159 # If it doesn't exist, create the file with the required data
160 f = open(os.path.join(getPresetpaths()[1], filename + '.py'), 'w')
161 f.write(data)
162 f.close()
163 return {'FINISHED'}
164 else:
165 return {'CANCELLED'}
166 else:
167 # If it exists then report an error
168 self.report({'ERROR_INVALID_INPUT'}, 'Preset Already Exists')
169 return {'CANCELLED'}
172 class ImportData(Operator):
173 """This operator handles importing existing presets"""
174 bl_idname = "sapling.importdata"
175 bl_label = "Import Preset"
177 filename: StringProperty()
179 def execute(self, context):
180 # Make sure the operator knows about the global variables
181 global settings, useSet
182 # Read the preset data into the global settings
183 try:
184 f = open(os.path.join(getPresetpaths()[0], self.filename), 'r')
185 except (FileNotFoundError, IOError):
186 f = open(os.path.join(getPresetpaths()[1], self.filename), 'r')
187 # Find the first non-comment, non-blank line, this must contain preset text (all on one line).
188 for settings in f:
189 if settings and (not settings.isspace()) and (not settings.startswith("#")):
190 break
191 f.close()
192 # print(settings)
193 settings = ast.literal_eval(settings)
195 # use old attractup
196 if type(settings['attractUp']) == float:
197 atr = settings['attractUp']
198 settings['attractUp'] = [0, 0, atr, atr]
200 # use old leaf rotations
201 if 'leafDownAngle' not in settings:
202 l = settings['levels']
203 settings['leafDownAngle'] = settings['downAngle'][min(l, 3)]
204 settings['leafDownAngleV'] = settings['downAngleV'][min(l, 3)]
205 settings['leafRotate'] = settings['rotate'][min(l, 3)]
206 settings['leafRotateV'] = settings['rotateV'][min(l, 3)]
208 # zero leaf bend
209 settings['bend'] = 0
211 # Set the flag to use the settings
212 useSet = True
213 return {'FINISHED'}
216 class PresetMenu(Menu):
217 """Create the preset menu by finding all preset files
218 in the preset directory"""
219 bl_idname = "SAPLING_MT_preset"
220 bl_label = "Presets"
222 def draw(self, context):
223 # Get all the sapling presets
224 presets = [a for a in os.listdir(getPresetpaths()[0]) if a[-3:] == '.py']
225 presets.extend([a for a in os.listdir(getPresetpaths()[1]) if a[-3:] == '.py'])
226 layout = self.layout
227 # Append all to the menu
228 for p in presets:
229 layout.operator("sapling.importdata", text=p[:-3]).filename = p
232 class AddTree(Operator):
233 bl_idname = "curve.tree_add"
234 bl_label = "Sapling: Add Tree"
235 bl_options = {'REGISTER', 'UNDO'}
237 # Keep the strings in memory, see T83360.
238 _objectList_static_strings = []
240 def objectList(self, context):
241 objects = AddTree._objectList_static_strings
242 objects.clear()
244 for obj in bpy.data.objects:
245 if (obj.type in {'MESH', 'CURVE', 'SURFACE'}) and (obj.name not in {'tree', 'leaves'}):
246 objects.append((obj.name, obj.name, ""))
248 if not objects:
249 objects.append(('NONE', "No objects", "No appropriate objects in the Scene"))
251 return objects
253 def update_tree(self, context):
254 self.do_update = True
256 def update_leaves(self, context):
257 if self.showLeaves:
258 self.do_update = True
259 else:
260 self.do_update = False
262 def no_update_tree(self, context):
263 self.do_update = False
265 do_update: BoolProperty(
266 name='Do Update',
267 default=True, options={'HIDDEN'}
269 chooseSet: EnumProperty(
270 name='Settings',
271 description='Choose the settings to modify',
272 items=settings,
273 default='0', update=no_update_tree
275 bevel: BoolProperty(
276 name='Bevel',
277 description='Whether the curve is beveled',
278 default=False, update=update_tree
280 prune: BoolProperty(
281 name='Prune',
282 description='Whether the tree is pruned',
283 default=False, update=update_tree
285 showLeaves: BoolProperty(
286 name='Show Leaves',
287 description='Whether the leaves are shown',
288 default=False, update=update_tree
290 useArm: BoolProperty(
291 name='Use Armature',
292 description='Whether the armature is generated',
293 default=False, update=update_tree
295 seed: IntProperty(
296 name='Random Seed',
297 description='The seed of the random number generator',
298 default=0, update=update_tree
300 handleType: IntProperty(
301 name='Handle Type',
302 description='The type of curve handles',
303 min=0,
304 max=1,
305 default=0, update=update_tree
307 levels: IntProperty(
308 name='Levels',
309 description='Number of recursive branches (Levels)',
310 min=1,
311 max=6,
312 soft_max=4,
313 default=3, update=update_tree
315 length: FloatVectorProperty(
316 name='Length',
317 description='The relative lengths of each branch level (nLength)',
318 min=0.000001,
319 default=[1, 0.3, 0.6, 0.45],
320 size=4, update=update_tree
322 lengthV: FloatVectorProperty(
323 name='Length Variation',
324 description='The relative length variations of each level (nLengthV)',
325 min=0.0,
326 max=1.0,
327 default=[0, 0, 0, 0],
328 size=4, update=update_tree
330 taperCrown: FloatProperty(
331 name='Taper Crown',
332 description='Shorten trunk splits toward outside of tree',
333 min=0.0,
334 soft_max=1.0,
335 default=0, update=update_tree
337 branches: IntVectorProperty(
338 name='Branches',
339 description='The number of branches grown at each level (nBranches)',
340 min=0,
341 default=[50, 30, 10, 10],
342 size=4, update=update_tree
344 curveRes: IntVectorProperty(
345 name='Curve Resolution',
346 description='The number of segments on each branch (nCurveRes)',
347 min=1,
348 default=[3, 5, 3, 1],
349 size=4, update=update_tree
351 curve: FloatVectorProperty(
352 name='Curvature',
353 description='The angle of the end of the branch (nCurve)',
354 default=[0, -40, -40, 0],
355 size=4, update=update_tree
357 curveV: FloatVectorProperty(
358 name='Curvature Variation',
359 description='Variation of the curvature (nCurveV)',
360 default=[20, 50, 75, 0],
361 size=4, update=update_tree
363 curveBack: FloatVectorProperty(
364 name='Back Curvature',
365 description='Curvature for the second half of a branch (nCurveBack)',
366 default=[0, 0, 0, 0],
367 size=4, update=update_tree
369 baseSplits: IntProperty(
370 name='Base Splits',
371 description='Number of trunk splits at its base (nBaseSplits)',
372 min=0,
373 default=0, update=update_tree
375 segSplits: FloatVectorProperty(
376 name='Segment Splits',
377 description='Number of splits per segment (nSegSplits)',
378 min=0,
379 soft_max=3,
380 default=[0, 0, 0, 0],
381 size=4, update=update_tree
383 splitByLen: BoolProperty(
384 name='Split relative to length',
385 description='Split proportional to branch length',
386 default=False, update=update_tree
388 rMode: EnumProperty(
389 name="", # "Branching Mode"
390 description='Branching and Rotation Mode',
391 items=branchmodes,
392 default="rotate", update=update_tree
394 splitAngle: FloatVectorProperty(
395 name='Split Angle',
396 description='Angle of branch splitting (nSplitAngle)',
397 default=[0, 0, 0, 0],
398 size=4, update=update_tree
400 splitAngleV: FloatVectorProperty(
401 name='Split Angle Variation',
402 description='Variation in the split angle (nSplitAngleV)',
403 default=[0, 0, 0, 0],
404 size=4, update=update_tree
406 scale: FloatProperty(
407 name='Scale',
408 description='The tree scale (Scale)',
409 min=0.0,
410 default=13.0, update=update_tree)
411 scaleV: FloatProperty(name='Scale Variation',
412 description='The variation in the tree scale (ScaleV)',
413 default=3.0, update=update_tree
415 attractUp: FloatVectorProperty(
416 name='Vertical Attraction',
417 description='Branch upward attraction',
418 default=[0, 0, 0, 0],
419 size=4, update=update_tree
421 attractOut: FloatVectorProperty(
422 name='Outward Attraction',
423 description='Branch outward attraction',
424 default=[0, 0, 0, 0],
425 min=0.0,
426 max=1.0,
427 size=4, update=update_tree
429 shape: EnumProperty(
430 name='Shape',
431 description='The overall shape of the tree (Shape)',
432 items=shapeList3,
433 default='7', update=update_tree
435 shapeS: EnumProperty(
436 name='Secondary Branches Shape',
437 description='The shape of secondary splits',
438 items=shapeList4,
439 default='4', update=update_tree
441 customShape: FloatVectorProperty(
442 name='Custom Shape',
443 description='custom shape branch length at (Base, Middle, Middle Position, Top)',
444 size=4,
445 min=.01,
446 max=1,
447 default=[.5, 1.0, .3, .5], update=update_tree
449 branchDist: FloatProperty(
450 name='Branch Distribution',
451 description='Adjust branch spacing to put more branches at the top or bottom of the tree',
452 min=0.1,
453 soft_max=10,
454 default=1.0, update=update_tree
456 nrings: IntProperty(
457 name='Branch Rings',
458 description='grow branches in rings',
459 min=0,
460 default=0, update=update_tree
462 baseSize: FloatProperty(
463 name='Trunk Height',
464 description='Fraction of tree height with no branches (Base Size)',
465 min=0.0,
466 max=1.0,
467 default=0.4, update=update_tree
469 baseSize_s: FloatProperty(
470 name='Secondary Base Size',
471 description='Factor to decrease base size for each level',
472 min=0.0,
473 max=1.0,
474 default=0.25, update=update_tree
476 splitHeight: FloatProperty(
477 name='Split Height',
478 description='Fraction of tree height with no splits',
479 min=0.0,
480 max=1.0,
481 default=0.2, update=update_tree
483 splitBias: FloatProperty(
484 name='splitBias',
485 description='Put more splits at the top or bottom of the tree',
486 soft_min=-2.0,
487 soft_max=2.0,
488 default=0.0, update=update_tree
490 ratio: FloatProperty(
491 name='Ratio',
492 description='Base radius size (Ratio)',
493 min=0.0,
494 default=0.015, update=update_tree
496 minRadius: FloatProperty(
497 name='Minimum Radius',
498 description='Minimum branch Radius',
499 min=0.0,
500 default=0.0, update=update_tree
502 closeTip: BoolProperty(
503 name='Close Tip',
504 description='Set radius at branch tips to zero',
505 default=False, update=update_tree
507 rootFlare: FloatProperty(
508 name='Root Flare',
509 description='Root radius factor',
510 min=1.0,
511 default=1.0, update=update_tree
513 autoTaper: BoolProperty(
514 name='Auto Taper',
515 description='Calculate taper automatically based on branch lengths',
516 default=True, update=update_tree
518 taper: FloatVectorProperty(
519 name='Taper',
520 description='The fraction of tapering on each branch (nTaper)',
521 min=0.0,
522 max=1.0,
523 default=[1, 1, 1, 1],
524 size=4, update=update_tree
526 radiusTweak: FloatVectorProperty(
527 name='Tweak Radius',
528 description='multiply radius by this factor',
529 min=0.0,
530 max=1.0,
531 default=[1, 1, 1, 1],
532 size=4, update=update_tree
534 ratioPower: FloatProperty(
535 name='Branch Radius Ratio',
536 description=('Power which defines the radius of a branch compared to '
537 'the radius of the branch it grew from (RatioPower)'),
538 min=0.0,
539 default=1.2, update=update_tree
541 downAngle: FloatVectorProperty(
542 name='Down Angle',
543 description=('The angle between a new branch and the one it grew '
544 'from (nDownAngle)'),
545 default=[90, 60, 45, 45],
546 size=4, update=update_tree
548 downAngleV: FloatVectorProperty(
549 name='Down Angle Variation',
550 description="Angle to decrease Down Angle by towards end of parent branch "
551 "(negative values add random variation)",
552 default=[0, -50, 10, 10],
553 size=4, update=update_tree
555 useOldDownAngle: BoolProperty(
556 name='Use old down angle variation',
557 default=False, update=update_tree
559 useParentAngle: BoolProperty(
560 name='Use parent angle',
561 description='(first level) Rotate branch to match parent branch',
562 default=True, update=update_tree
564 rotate: FloatVectorProperty(
565 name='Rotate Angle',
566 description="The angle of a new branch around the one it grew from "
567 "(negative values rotate opposite from the previous)",
568 default=[137.5, 137.5, 137.5, 137.5],
569 size=4, update=update_tree
571 rotateV: FloatVectorProperty(
572 name='Rotate Angle Variation',
573 description='Variation in the rotate angle (nRotateV)',
574 default=[0, 0, 0, 0],
575 size=4, update=update_tree
577 scale0: FloatProperty(
578 name='Radius Scale',
579 description='The scale of the trunk radius (0Scale)',
580 min=0.0,
581 default=1.0, update=update_tree
583 scaleV0: FloatProperty(
584 name='Radius Scale Variation',
585 description='Variation in the radius scale (0ScaleV)',
586 min=0.0,
587 max=1.0,
588 default=0.2, update=update_tree
590 pruneWidth: FloatProperty(
591 name='Prune Width',
592 description='The width of the envelope (PruneWidth)',
593 min=0.0,
594 default=0.4, update=update_tree
596 pruneBase: FloatProperty(
597 name='Prune Base Height',
598 description='The height of the base of the envelope, bound by trunk height',
599 min=0.0,
600 max=1.0,
601 default=0.3, update=update_tree
603 pruneWidthPeak: FloatProperty(
604 name='Prune Width Peak',
605 description=("Fraction of envelope height where the maximum width "
606 "occurs (PruneWidthPeak)"),
607 min=0.0,
608 default=0.6, update=update_tree
610 prunePowerHigh: FloatProperty(
611 name='Prune Power High',
612 description=('Power which determines the shape of the upper portion '
613 'of the envelope (PrunePowerHigh)'),
614 default=0.5, update=update_tree
616 prunePowerLow: FloatProperty(
617 name='Prune Power Low',
618 description=('Power which determines the shape of the lower portion '
619 'of the envelope (PrunePowerLow)'),
620 default=0.001, update=update_tree
622 pruneRatio: FloatProperty(
623 name='Prune Ratio',
624 description='Proportion of pruned length (PruneRatio)',
625 min=0.0,
626 max=1.0,
627 default=1.0, update=update_tree
629 leaves: IntProperty(
630 name='Leaves',
631 description="Maximum number of leaves per branch (negative values grow "
632 "leaves from branch tip (palmate compound leaves))",
633 default=25, update=update_tree
635 leafDownAngle: FloatProperty(
636 name='Leaf Down Angle',
637 description='The angle between a new leaf and the branch it grew from',
638 default=45, update=update_leaves
640 leafDownAngleV: FloatProperty(
641 name='Leaf Down Angle Variation',
642 description="Angle to decrease Down Angle by towards end of parent branch "
643 "(negative values add random variation)",
644 default=10, update=update_tree
646 leafRotate: FloatProperty(
647 name='Leaf Rotate Angle',
648 description="The angle of a new leaf around the one it grew from "
649 "(negative values rotate opposite from previous)",
650 default=137.5, update=update_tree
652 leafRotateV: FloatProperty(
653 name='Leaf Rotate Angle Variation',
654 description='Variation in the rotate angle',
655 default=0.0, update=update_leaves
657 leafScale: FloatProperty(
658 name='Leaf Scale',
659 description='The scaling applied to the whole leaf (LeafScale)',
660 min=0.0,
661 default=0.17, update=update_leaves
663 leafScaleX: FloatProperty(
664 name='Leaf Scale X',
665 description=('The scaling applied to the x direction of the leaf '
666 '(LeafScaleX)'),
667 min=0.0,
668 default=1.0, update=update_leaves
670 leafScaleT: FloatProperty(
671 name='Leaf Scale Taper',
672 description='scale leaves toward the tip or base of the patent branch',
673 min=-1.0,
674 max=1.0,
675 default=0.0, update=update_leaves
677 leafScaleV: FloatProperty(
678 name='Leaf Scale Variation',
679 description='randomize leaf scale',
680 min=0.0,
681 max=1.0,
682 default=0.0, update=update_leaves
684 leafShape: EnumProperty(
685 name='Leaf Shape',
686 description='The shape of the leaves',
687 items=(('hex', 'Hexagonal', '0'), ('rect', 'Rectangular', '1'),
688 ('dFace', 'DupliFaces', '2'), ('dVert', 'DupliVerts', '3')),
689 default='hex', update=update_leaves
691 leafDupliObj: EnumProperty(
692 name='Leaf Object',
693 description='Object to use for leaf instancing if Leaf Shape is DupliFaces or DupliVerts',
694 items=objectList,
695 update=update_leaves
697 bend: FloatProperty(
698 name='Leaf Bend',
699 description='The proportion of bending applied to the leaf (Bend)',
700 min=0.0,
701 max=1.0,
702 default=0.0, update=update_leaves
704 leafangle: FloatProperty(
705 name='Leaf Angle',
706 description='Leaf vertical attraction',
707 default=0.0, update=update_leaves
709 horzLeaves: BoolProperty(
710 name='Horizontal leaves',
711 description='Leaves face upwards',
712 default=True, update=update_leaves
714 leafDist: EnumProperty(
715 name='Leaf Distribution',
716 description='The way leaves are distributed on branches',
717 items=shapeList4,
718 default='6', update=update_tree
720 bevelRes: IntProperty(
721 name='Bevel Resolution',
722 description='The bevel resolution of the curves',
723 min=0,
724 max=32,
725 default=0, update=update_tree
727 resU: IntProperty(
728 name='Curve Resolution',
729 description='The resolution along the curves',
730 min=1,
731 default=4, update=update_tree
733 handleType: EnumProperty(
734 name='Handle Type',
735 description='The type of handles used in the spline',
736 items=handleList,
737 default='0', update=update_tree
739 armAnim: BoolProperty(
740 name='Armature Animation',
741 description='Whether animation is added to the armature',
742 default=False, update=update_tree
744 previewArm: BoolProperty(
745 name='Fast Preview',
746 description='Disable armature modifier, hide tree, and set bone display to wire, for fast playback',
747 # Disable skin modifier and hide tree and armature, for fast playback
748 default=False, update=update_tree
750 leafAnim: BoolProperty(
751 name='Leaf Animation',
752 description='Whether animation is added to the leaves',
753 default=False, update=update_tree
755 frameRate: FloatProperty(
756 name='Animation Speed',
757 description=('Adjust speed of animation, relative to scene frame rate'),
758 min=0.001,
759 default=1, update=update_tree
761 loopFrames: IntProperty(
762 name='Loop Frames',
763 description='Number of frames to make the animation loop for, zero is disabled',
764 min=0,
765 default=0, update=update_tree
768 windSpeed = FloatProperty(
769 name='Wind Speed',
770 description='The wind speed to apply to the armature',
771 default=2.0, update=update_tree
773 windGust = FloatProperty(
774 name='Wind Gust',
775 description='The greatest increase over Wind Speed',
776 default=0.0, update=update_tree
779 wind: FloatProperty(
780 name='Overall Wind Strength',
781 description='The intensity of the wind to apply to the armature',
782 default=1.0, update=update_tree
784 gust: FloatProperty(
785 name='Wind Gust Strength',
786 description='The amount of directional movement, (from the positive Y direction)',
787 default=1.0, update=update_tree
789 gustF: FloatProperty(
790 name='Wind Gust Fequency',
791 description='The Frequency of directional movement',
792 default=0.075, update=update_tree
794 af1: FloatProperty(
795 name='Amplitude',
796 description='Multiplier for noise amplitude',
797 default=1.0, update=update_tree
799 af2: FloatProperty(
800 name='Frequency',
801 description='Multiplier for noise fequency',
802 default=1.0, update=update_tree
804 af3: FloatProperty(
805 name='Randomness',
806 description='Random offset in noise',
807 default=4.0, update=update_tree
809 makeMesh: BoolProperty(
810 name='Make Mesh',
811 description='Convert curves to mesh, uses skin modifier, enables armature simplification',
812 default=False, update=update_tree
814 armLevels: IntProperty(
815 name='Armature Levels',
816 description='Number of branching levels to make bones for, 0 is all levels',
817 min=0,
818 default=2, update=update_tree
820 boneStep: IntVectorProperty(
821 name='Bone Length',
822 description='Number of stem segments per bone',
823 min=1,
824 default=[1, 1, 1, 1],
825 size=4, update=update_tree
827 presetName: StringProperty(
828 name='Preset Name',
829 description='The name of the preset to be saved',
830 default='',
831 subtype='FILE_NAME', update=no_update_tree
833 limitImport: BoolProperty(
834 name='Limit Import',
835 description='Limited imported tree to 2 levels & no leaves for speed',
836 default=True, update=no_update_tree
838 overwrite: BoolProperty(
839 name='Overwrite',
840 description='When checked, overwrite existing preset files when saving',
841 default=False, update=no_update_tree
844 startCurv = FloatProperty(
845 name='Trunk Starting Angle',
846 description=('The angle between vertical and the starting direction'
847 'of the trunk'),
848 min=0.0,
849 max=360,
850 default=0.0, update=update_tree
854 @classmethod
855 def poll(cls, context):
856 return context.mode == 'OBJECT'
858 def draw(self, context):
859 layout = self.layout
861 # Branch specs
862 # layout.label(text='Tree Definition')
864 layout.prop(self, 'chooseSet')
866 if self.chooseSet == '0':
867 box = layout.box()
868 box.label(text="Geometry:")
869 box.prop(self, 'bevel')
871 row = box.row()
872 row.prop(self, 'bevelRes')
873 row.prop(self, 'resU')
875 box.prop(self, 'handleType')
876 box.prop(self, 'shape')
878 col = box.column()
879 col.prop(self, 'customShape')
881 row = box.row()
882 box.prop(self, 'shapeS')
883 box.prop(self, 'branchDist')
884 box.prop(self, 'nrings')
885 box.prop(self, 'seed')
887 box.label(text="Tree Scale:")
888 row = box.row()
889 row.prop(self, 'scale')
890 row.prop(self, 'scaleV')
892 # Here we create a dict of all the properties.
893 # Unfortunately as_keyword doesn't work with vector properties,
894 # so we need something custom. This is it
895 data = []
896 for a, b in (self.as_keywords(
897 ignore=("chooseSet", "presetName", "limitImport",
898 "do_update", "overwrite", "leafDupliObj"))).items():
899 # If the property is a vector property then add the slice to the list
900 try:
901 len(b)
902 data.append((a, b[:]))
903 # Otherwise, it is fine so just add it
904 except:
905 data.append((a, b))
906 # Create the dict from the list
907 data = dict(data)
909 row = box.row()
910 row.prop(self, 'presetName')
911 # Send the data dict and the file name to the exporter
912 row.operator('sapling.exportdata').data = repr([repr(data), self.presetName, self.overwrite])
913 row = box.row()
914 row.label(text=" ")
915 row.prop(self, 'overwrite')
916 row = box.row()
917 row.menu('SAPLING_MT_preset', text='Load Preset')
918 row.prop(self, 'limitImport')
920 elif self.chooseSet == '1':
921 box = layout.box()
922 box.label(text="Branch Radius:")
924 row = box.row()
925 row.prop(self, 'bevel')
926 row.prop(self, 'bevelRes')
928 box.prop(self, 'ratio')
929 row = box.row()
930 row.prop(self, 'scale0')
931 row.prop(self, 'scaleV0')
932 box.prop(self, 'ratioPower')
934 box.prop(self, 'minRadius')
935 box.prop(self, 'closeTip')
936 box.prop(self, 'rootFlare')
938 box.prop(self, 'autoTaper')
940 split = box.split()
941 col = split.column()
942 col.prop(self, 'taper')
943 col = split.column()
944 col.prop(self, 'radiusTweak')
946 elif self.chooseSet == '2':
947 box = layout.box()
948 box.label(text="Branch Splitting:")
949 box.prop(self, 'levels')
950 box.prop(self, 'baseSplits')
951 row = box.row()
952 row.prop(self, 'baseSize')
953 row.prop(self, 'baseSize_s')
954 box.prop(self, 'splitHeight')
955 box.prop(self, 'splitBias')
956 box.prop(self, 'splitByLen')
958 split = box.split()
960 col = split.column()
961 col.prop(self, 'branches')
962 col.prop(self, 'splitAngle')
963 col.prop(self, 'rotate')
964 col.prop(self, 'attractOut')
966 col = split.column()
967 col.prop(self, 'segSplits')
968 col.prop(self, 'splitAngleV')
969 col.prop(self, 'rotateV')
971 col.label(text="Branching Mode:")
972 col.prop(self, 'rMode')
974 box.column().prop(self, 'curveRes')
976 elif self.chooseSet == '3':
977 box = layout.box()
978 box.label(text="Branch Growth:")
980 box.prop(self, 'taperCrown')
982 split = box.split()
984 col = split.column()
985 col.prop(self, 'length')
986 col.prop(self, 'downAngle')
987 col.prop(self, 'curve')
988 col.prop(self, 'curveBack')
990 col = split.column()
991 col.prop(self, 'lengthV')
992 col.prop(self, 'downAngleV')
993 col.prop(self, 'curveV')
994 col.prop(self, 'attractUp')
996 box.prop(self, 'useOldDownAngle')
997 box.prop(self, 'useParentAngle')
999 elif self.chooseSet == '4':
1000 box = layout.box()
1001 box.label(text="Prune:")
1002 box.prop(self, 'prune')
1003 box.prop(self, 'pruneRatio')
1004 row = box.row()
1005 row.prop(self, 'pruneWidth')
1006 row.prop(self, 'pruneBase')
1007 box.prop(self, 'pruneWidthPeak')
1009 row = box.row()
1010 row.prop(self, 'prunePowerHigh')
1011 row.prop(self, 'prunePowerLow')
1013 elif self.chooseSet == '5':
1014 box = layout.box()
1015 box.label(text="Leaves:")
1016 box.prop(self, 'showLeaves')
1017 box.prop(self, 'leafShape')
1018 box.prop(self, 'leafDupliObj')
1019 box.prop(self, 'leaves')
1020 box.prop(self, 'leafDist')
1022 box.label(text="")
1023 row = box.row()
1024 row.prop(self, 'leafDownAngle')
1025 row.prop(self, 'leafDownAngleV')
1027 row = box.row()
1028 row.prop(self, 'leafRotate')
1029 row.prop(self, 'leafRotateV')
1030 box.label(text="")
1032 row = box.row()
1033 row.prop(self, 'leafScale')
1034 row.prop(self, 'leafScaleX')
1036 row = box.row()
1037 row.prop(self, 'leafScaleT')
1038 row.prop(self, 'leafScaleV')
1040 box.prop(self, 'horzLeaves')
1041 box.prop(self, 'leafangle')
1043 # box.label(text=" ")
1044 # box.prop(self, 'bend')
1046 elif self.chooseSet == '6':
1047 box = layout.box()
1048 box.label(text="Armature:")
1049 row = box.row()
1050 row.prop(self, 'useArm')
1051 box.prop(self, 'makeMesh')
1052 box.label(text="Armature Simplification:")
1053 box.prop(self, 'armLevels')
1054 box.prop(self, 'boneStep')
1056 elif self.chooseSet == '7':
1057 box = layout.box()
1058 box.label(text="Finalize All Other Settings First!")
1059 box.prop(self, 'armAnim')
1060 box.prop(self, 'leafAnim')
1061 box.prop(self, 'previewArm')
1062 box.prop(self, 'frameRate')
1063 box.prop(self, 'loopFrames')
1065 # row = box.row()
1066 # row.prop(self, 'windSpeed')
1067 # row.prop(self, 'windGust')
1069 box.label(text='Wind Settings:')
1070 box.prop(self, 'wind')
1071 row = box.row()
1072 row.prop(self, 'gust')
1073 row.prop(self, 'gustF')
1075 box.label(text='Leaf Wind Settings:')
1076 box.prop(self, 'af1')
1077 box.prop(self, 'af2')
1078 box.prop(self, 'af3')
1080 def execute(self, context):
1081 # Ensure the use of the global variables
1082 global settings, useSet
1083 start_time = time.time()
1085 # If we need to set the properties from a preset then do it here
1086 if useSet:
1087 for a, b in settings.items():
1088 setattr(self, a, b)
1089 if self.limitImport:
1090 setattr(self, 'levels', min(settings['levels'], 2))
1091 setattr(self, 'showLeaves', False)
1092 useSet = False
1093 if not self.do_update:
1094 return {'PASS_THROUGH'}
1095 utils.addTree(self)
1096 # cProfile.runctx("addTree(self)", globals(), locals())
1097 print("Tree creation in %0.1fs" % (time.time() - start_time))
1099 return {'FINISHED'}
1101 def invoke(self, context, event):
1102 bpy.ops.sapling.importdata(filename="callistemon.py")
1103 return self.execute(context)
1106 def menu_func(self, context):
1107 self.layout.operator(AddTree.bl_idname, text="Sapling Tree Gen", icon='CURVE_DATA')
1109 classes = (
1110 AddTree,
1111 PresetMenu,
1112 ImportData,
1113 ExportData,
1116 def register():
1117 from bpy.utils import register_class
1118 for cls in classes:
1119 register_class(cls)
1120 bpy.types.VIEW3D_MT_curve_add.append(menu_func)
1123 def unregister():
1124 from bpy.utils import unregister_class
1125 for cls in reversed(classes):
1126 unregister_class(cls)
1127 bpy.types.VIEW3D_MT_curve_add.remove(menu_func)
1130 if __name__ == "__main__":
1131 register()