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