Cleanup: autopep8 for 3DS i/o
[blender-addons.git] / add_curve_sapling / __init__.py
blobbead5ee712c6e29a036403f2c18131fac4bfeb02
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 # Find the first non-comment, non-blank line, this must contain preset text (all on one line).
186 for settings in f:
187 if settings and (not settings.startswith("#")):
188 break
189 f.close()
190 # print(settings)
191 settings = ast.literal_eval(settings)
193 # use old attractup
194 if type(settings['attractUp']) == float:
195 atr = settings['attractUp']
196 settings['attractUp'] = [0, 0, atr, atr]
198 # use old leaf rotations
199 if 'leafDownAngle' not in settings:
200 l = settings['levels']
201 settings['leafDownAngle'] = settings['downAngle'][min(l, 3)]
202 settings['leafDownAngleV'] = settings['downAngleV'][min(l, 3)]
203 settings['leafRotate'] = settings['rotate'][min(l, 3)]
204 settings['leafRotateV'] = settings['rotateV'][min(l, 3)]
206 # zero leaf bend
207 settings['bend'] = 0
209 # Set the flag to use the settings
210 useSet = True
211 return {'FINISHED'}
214 class PresetMenu(Menu):
215 """Create the preset menu by finding all preset files
216 in the preset directory"""
217 bl_idname = "SAPLING_MT_preset"
218 bl_label = "Presets"
220 def draw(self, context):
221 # Get all the sapling presets
222 presets = [a for a in os.listdir(getPresetpaths()[0]) if a[-3:] == '.py']
223 presets.extend([a for a in os.listdir(getPresetpaths()[1]) if a[-3:] == '.py'])
224 layout = self.layout
225 # Append all to the menu
226 for p in presets:
227 layout.operator("sapling.importdata", text=p[:-3]).filename = p
230 class AddTree(Operator):
231 bl_idname = "curve.tree_add"
232 bl_label = "Sapling: Add Tree"
233 bl_options = {'REGISTER', 'UNDO'}
235 # Keep the strings in memory, see T83360.
236 _objectList_static_strings = []
238 def objectList(self, context):
239 objects = AddTree._objectList_static_strings
240 objects.clear()
242 for obj in bpy.data.objects:
243 if (obj.type in {'MESH', 'CURVE', 'SURFACE'}) and (obj.name not in {'tree', 'leaves'}):
244 objects.append((obj.name, obj.name, ""))
246 if not objects:
247 objects.append(('NONE', "No objects", "No appropriate objects in the Scene"))
249 return objects
251 def update_tree(self, context):
252 self.do_update = True
254 def update_leaves(self, context):
255 if self.showLeaves:
256 self.do_update = True
257 else:
258 self.do_update = False
260 def no_update_tree(self, context):
261 self.do_update = False
263 do_update: BoolProperty(
264 name='Do Update',
265 default=True, options={'HIDDEN'}
267 chooseSet: EnumProperty(
268 name='Settings',
269 description='Choose the settings to modify',
270 items=settings,
271 default='0', update=no_update_tree
273 bevel: BoolProperty(
274 name='Bevel',
275 description='Whether the curve is beveled',
276 default=False, update=update_tree
278 prune: BoolProperty(
279 name='Prune',
280 description='Whether the tree is pruned',
281 default=False, update=update_tree
283 showLeaves: BoolProperty(
284 name='Show Leaves',
285 description='Whether the leaves are shown',
286 default=False, update=update_tree
288 useArm: BoolProperty(
289 name='Use Armature',
290 description='Whether the armature is generated',
291 default=False, update=update_tree
293 seed: IntProperty(
294 name='Random Seed',
295 description='The seed of the random number generator',
296 default=0, update=update_tree
298 handleType: IntProperty(
299 name='Handle Type',
300 description='The type of curve handles',
301 min=0,
302 max=1,
303 default=0, update=update_tree
305 levels: IntProperty(
306 name='Levels',
307 description='Number of recursive branches (Levels)',
308 min=1,
309 max=6,
310 soft_max=4,
311 default=3, update=update_tree
313 length: FloatVectorProperty(
314 name='Length',
315 description='The relative lengths of each branch level (nLength)',
316 min=0.000001,
317 default=[1, 0.3, 0.6, 0.45],
318 size=4, update=update_tree
320 lengthV: FloatVectorProperty(
321 name='Length Variation',
322 description='The relative length variations of each level (nLengthV)',
323 min=0.0,
324 max=1.0,
325 default=[0, 0, 0, 0],
326 size=4, update=update_tree
328 taperCrown: FloatProperty(
329 name='Taper Crown',
330 description='Shorten trunk splits toward outside of tree',
331 min=0.0,
332 soft_max=1.0,
333 default=0, update=update_tree
335 branches: IntVectorProperty(
336 name='Branches',
337 description='The number of branches grown at each level (nBranches)',
338 min=0,
339 default=[50, 30, 10, 10],
340 size=4, update=update_tree
342 curveRes: IntVectorProperty(
343 name='Curve Resolution',
344 description='The number of segments on each branch (nCurveRes)',
345 min=1,
346 default=[3, 5, 3, 1],
347 size=4, update=update_tree
349 curve: FloatVectorProperty(
350 name='Curvature',
351 description='The angle of the end of the branch (nCurve)',
352 default=[0, -40, -40, 0],
353 size=4, update=update_tree
355 curveV: FloatVectorProperty(
356 name='Curvature Variation',
357 description='Variation of the curvature (nCurveV)',
358 default=[20, 50, 75, 0],
359 size=4, update=update_tree
361 curveBack: FloatVectorProperty(
362 name='Back Curvature',
363 description='Curvature for the second half of a branch (nCurveBack)',
364 default=[0, 0, 0, 0],
365 size=4, update=update_tree
367 baseSplits: IntProperty(
368 name='Base Splits',
369 description='Number of trunk splits at its base (nBaseSplits)',
370 min=0,
371 default=0, update=update_tree
373 segSplits: FloatVectorProperty(
374 name='Segment Splits',
375 description='Number of splits per segment (nSegSplits)',
376 min=0,
377 soft_max=3,
378 default=[0, 0, 0, 0],
379 size=4, update=update_tree
381 splitByLen: BoolProperty(
382 name='Split relative to length',
383 description='Split proportional to branch length',
384 default=False, update=update_tree
386 rMode: EnumProperty(
387 name="", # "Branching Mode"
388 description='Branching and Rotation Mode',
389 items=branchmodes,
390 default="rotate", update=update_tree
392 splitAngle: FloatVectorProperty(
393 name='Split Angle',
394 description='Angle of branch splitting (nSplitAngle)',
395 default=[0, 0, 0, 0],
396 size=4, update=update_tree
398 splitAngleV: FloatVectorProperty(
399 name='Split Angle Variation',
400 description='Variation in the split angle (nSplitAngleV)',
401 default=[0, 0, 0, 0],
402 size=4, update=update_tree
404 scale: FloatProperty(
405 name='Scale',
406 description='The tree scale (Scale)',
407 min=0.0,
408 default=13.0, update=update_tree)
409 scaleV: FloatProperty(name='Scale Variation',
410 description='The variation in the tree scale (ScaleV)',
411 default=3.0, update=update_tree
413 attractUp: FloatVectorProperty(
414 name='Vertical Attraction',
415 description='Branch upward attraction',
416 default=[0, 0, 0, 0],
417 size=4, update=update_tree
419 attractOut: FloatVectorProperty(
420 name='Outward Attraction',
421 description='Branch outward attraction',
422 default=[0, 0, 0, 0],
423 min=0.0,
424 max=1.0,
425 size=4, update=update_tree
427 shape: EnumProperty(
428 name='Shape',
429 description='The overall shape of the tree (Shape)',
430 items=shapeList3,
431 default='7', update=update_tree
433 shapeS: EnumProperty(
434 name='Secondary Branches Shape',
435 description='The shape of secondary splits',
436 items=shapeList4,
437 default='4', update=update_tree
439 customShape: FloatVectorProperty(
440 name='Custom Shape',
441 description='custom shape branch length at (Base, Middle, Middle Position, Top)',
442 size=4,
443 min=.01,
444 max=1,
445 default=[.5, 1.0, .3, .5], update=update_tree
447 branchDist: FloatProperty(
448 name='Branch Distribution',
449 description='Adjust branch spacing to put more branches at the top or bottom of the tree',
450 min=0.1,
451 soft_max=10,
452 default=1.0, update=update_tree
454 nrings: IntProperty(
455 name='Branch Rings',
456 description='grow branches in rings',
457 min=0,
458 default=0, update=update_tree
460 baseSize: FloatProperty(
461 name='Trunk Height',
462 description='Fraction of tree height with no branches (Base Size)',
463 min=0.0,
464 max=1.0,
465 default=0.4, update=update_tree
467 baseSize_s: FloatProperty(
468 name='Secondary Base Size',
469 description='Factor to decrease base size for each level',
470 min=0.0,
471 max=1.0,
472 default=0.25, update=update_tree
474 splitHeight: FloatProperty(
475 name='Split Height',
476 description='Fraction of tree height with no splits',
477 min=0.0,
478 max=1.0,
479 default=0.2, update=update_tree
481 splitBias: FloatProperty(
482 name='splitBias',
483 description='Put more splits at the top or bottom of the tree',
484 soft_min=-2.0,
485 soft_max=2.0,
486 default=0.0, update=update_tree
488 ratio: FloatProperty(
489 name='Ratio',
490 description='Base radius size (Ratio)',
491 min=0.0,
492 default=0.015, update=update_tree
494 minRadius: FloatProperty(
495 name='Minimum Radius',
496 description='Minimum branch Radius',
497 min=0.0,
498 default=0.0, update=update_tree
500 closeTip: BoolProperty(
501 name='Close Tip',
502 description='Set radius at branch tips to zero',
503 default=False, update=update_tree
505 rootFlare: FloatProperty(
506 name='Root Flare',
507 description='Root radius factor',
508 min=1.0,
509 default=1.0, update=update_tree
511 autoTaper: BoolProperty(
512 name='Auto Taper',
513 description='Calculate taper automatically based on branch lengths',
514 default=True, update=update_tree
516 taper: FloatVectorProperty(
517 name='Taper',
518 description='The fraction of tapering on each branch (nTaper)',
519 min=0.0,
520 max=1.0,
521 default=[1, 1, 1, 1],
522 size=4, update=update_tree
524 radiusTweak: FloatVectorProperty(
525 name='Tweak Radius',
526 description='multiply radius by this factor',
527 min=0.0,
528 max=1.0,
529 default=[1, 1, 1, 1],
530 size=4, update=update_tree
532 ratioPower: FloatProperty(
533 name='Branch Radius Ratio',
534 description=('Power which defines the radius of a branch compared to '
535 'the radius of the branch it grew from (RatioPower)'),
536 min=0.0,
537 default=1.2, update=update_tree
539 downAngle: FloatVectorProperty(
540 name='Down Angle',
541 description=('The angle between a new branch and the one it grew '
542 'from (nDownAngle)'),
543 default=[90, 60, 45, 45],
544 size=4, update=update_tree
546 downAngleV: FloatVectorProperty(
547 name='Down Angle Variation',
548 description="Angle to decrease Down Angle by towards end of parent branch "
549 "(negative values add random variation)",
550 default=[0, -50, 10, 10],
551 size=4, update=update_tree
553 useOldDownAngle: BoolProperty(
554 name='Use old down angle variation',
555 default=False, update=update_tree
557 useParentAngle: BoolProperty(
558 name='Use parent angle',
559 description='(first level) Rotate branch to match parent branch',
560 default=True, update=update_tree
562 rotate: FloatVectorProperty(
563 name='Rotate Angle',
564 description="The angle of a new branch around the one it grew from "
565 "(negative values rotate opposite from the previous)",
566 default=[137.5, 137.5, 137.5, 137.5],
567 size=4, update=update_tree
569 rotateV: FloatVectorProperty(
570 name='Rotate Angle Variation',
571 description='Variation in the rotate angle (nRotateV)',
572 default=[0, 0, 0, 0],
573 size=4, update=update_tree
575 scale0: FloatProperty(
576 name='Radius Scale',
577 description='The scale of the trunk radius (0Scale)',
578 min=0.0,
579 default=1.0, update=update_tree
581 scaleV0: FloatProperty(
582 name='Radius Scale Variation',
583 description='Variation in the radius scale (0ScaleV)',
584 min=0.0,
585 max=1.0,
586 default=0.2, update=update_tree
588 pruneWidth: FloatProperty(
589 name='Prune Width',
590 description='The width of the envelope (PruneWidth)',
591 min=0.0,
592 default=0.4, update=update_tree
594 pruneBase: FloatProperty(
595 name='Prune Base Height',
596 description='The height of the base of the envelope, bound by trunk height',
597 min=0.0,
598 max=1.0,
599 default=0.3, update=update_tree
601 pruneWidthPeak: FloatProperty(
602 name='Prune Width Peak',
603 description=("Fraction of envelope height where the maximum width "
604 "occurs (PruneWidthPeak)"),
605 min=0.0,
606 default=0.6, update=update_tree
608 prunePowerHigh: FloatProperty(
609 name='Prune Power High',
610 description=('Power which determines the shape of the upper portion '
611 'of the envelope (PrunePowerHigh)'),
612 default=0.5, update=update_tree
614 prunePowerLow: FloatProperty(
615 name='Prune Power Low',
616 description=('Power which determines the shape of the lower portion '
617 'of the envelope (PrunePowerLow)'),
618 default=0.001, update=update_tree
620 pruneRatio: FloatProperty(
621 name='Prune Ratio',
622 description='Proportion of pruned length (PruneRatio)',
623 min=0.0,
624 max=1.0,
625 default=1.0, update=update_tree
627 leaves: IntProperty(
628 name='Leaves',
629 description="Maximum number of leaves per branch (negative values grow "
630 "leaves from branch tip (palmate compound leaves))",
631 default=25, update=update_tree
633 leafDownAngle: FloatProperty(
634 name='Leaf Down Angle',
635 description='The angle between a new leaf and the branch it grew from',
636 default=45, update=update_leaves
638 leafDownAngleV: FloatProperty(
639 name='Leaf Down Angle Variation',
640 description="Angle to decrease Down Angle by towards end of parent branch "
641 "(negative values add random variation)",
642 default=10, update=update_tree
644 leafRotate: FloatProperty(
645 name='Leaf Rotate Angle',
646 description="The angle of a new leaf around the one it grew from "
647 "(negative values rotate opposite from previous)",
648 default=137.5, update=update_tree
650 leafRotateV: FloatProperty(
651 name='Leaf Rotate Angle Variation',
652 description='Variation in the rotate angle',
653 default=0.0, update=update_leaves
655 leafScale: FloatProperty(
656 name='Leaf Scale',
657 description='The scaling applied to the whole leaf (LeafScale)',
658 min=0.0,
659 default=0.17, update=update_leaves
661 leafScaleX: FloatProperty(
662 name='Leaf Scale X',
663 description=('The scaling applied to the x direction of the leaf '
664 '(LeafScaleX)'),
665 min=0.0,
666 default=1.0, update=update_leaves
668 leafScaleT: FloatProperty(
669 name='Leaf Scale Taper',
670 description='scale leaves toward the tip or base of the patent branch',
671 min=-1.0,
672 max=1.0,
673 default=0.0, update=update_leaves
675 leafScaleV: FloatProperty(
676 name='Leaf Scale Variation',
677 description='randomize leaf scale',
678 min=0.0,
679 max=1.0,
680 default=0.0, update=update_leaves
682 leafShape: EnumProperty(
683 name='Leaf Shape',
684 description='The shape of the leaves',
685 items=(('hex', 'Hexagonal', '0'), ('rect', 'Rectangular', '1'),
686 ('dFace', 'DupliFaces', '2'), ('dVert', 'DupliVerts', '3')),
687 default='hex', update=update_leaves
689 leafDupliObj: EnumProperty(
690 name='Leaf Object',
691 description='Object to use for leaf instancing if Leaf Shape is DupliFaces or DupliVerts',
692 items=objectList,
693 update=update_leaves
695 bend: FloatProperty(
696 name='Leaf Bend',
697 description='The proportion of bending applied to the leaf (Bend)',
698 min=0.0,
699 max=1.0,
700 default=0.0, update=update_leaves
702 leafangle: FloatProperty(
703 name='Leaf Angle',
704 description='Leaf vertical attraction',
705 default=0.0, update=update_leaves
707 horzLeaves: BoolProperty(
708 name='Horizontal leaves',
709 description='Leaves face upwards',
710 default=True, update=update_leaves
712 leafDist: EnumProperty(
713 name='Leaf Distribution',
714 description='The way leaves are distributed on branches',
715 items=shapeList4,
716 default='6', update=update_tree
718 bevelRes: IntProperty(
719 name='Bevel Resolution',
720 description='The bevel resolution of the curves',
721 min=0,
722 max=32,
723 default=0, update=update_tree
725 resU: IntProperty(
726 name='Curve Resolution',
727 description='The resolution along the curves',
728 min=1,
729 default=4, update=update_tree
731 handleType: EnumProperty(
732 name='Handle Type',
733 description='The type of handles used in the spline',
734 items=handleList,
735 default='0', update=update_tree
737 armAnim: BoolProperty(
738 name='Armature Animation',
739 description='Whether animation is added to the armature',
740 default=False, update=update_tree
742 previewArm: BoolProperty(
743 name='Fast Preview',
744 description='Disable armature modifier, hide tree, and set bone display to wire, for fast playback',
745 # Disable skin modifier and hide tree and armature, for fast playback
746 default=False, update=update_tree
748 leafAnim: BoolProperty(
749 name='Leaf Animation',
750 description='Whether animation is added to the leaves',
751 default=False, update=update_tree
753 frameRate: FloatProperty(
754 name='Animation Speed',
755 description=('Adjust speed of animation, relative to scene frame rate'),
756 min=0.001,
757 default=1, update=update_tree
759 loopFrames: IntProperty(
760 name='Loop Frames',
761 description='Number of frames to make the animation loop for, zero is disabled',
762 min=0,
763 default=0, update=update_tree
766 windSpeed = FloatProperty(
767 name='Wind Speed',
768 description='The wind speed to apply to the armature',
769 default=2.0, update=update_tree
771 windGust = FloatProperty(
772 name='Wind Gust',
773 description='The greatest increase over Wind Speed',
774 default=0.0, update=update_tree
777 wind: FloatProperty(
778 name='Overall Wind Strength',
779 description='The intensity of the wind to apply to the armature',
780 default=1.0, update=update_tree
782 gust: FloatProperty(
783 name='Wind Gust Strength',
784 description='The amount of directional movement, (from the positive Y direction)',
785 default=1.0, update=update_tree
787 gustF: FloatProperty(
788 name='Wind Gust Fequency',
789 description='The Frequency of directional movement',
790 default=0.075, update=update_tree
792 af1: FloatProperty(
793 name='Amplitude',
794 description='Multiplier for noise amplitude',
795 default=1.0, update=update_tree
797 af2: FloatProperty(
798 name='Frequency',
799 description='Multiplier for noise fequency',
800 default=1.0, update=update_tree
802 af3: FloatProperty(
803 name='Randomness',
804 description='Random offset in noise',
805 default=4.0, update=update_tree
807 makeMesh: BoolProperty(
808 name='Make Mesh',
809 description='Convert curves to mesh, uses skin modifier, enables armature simplification',
810 default=False, update=update_tree
812 armLevels: IntProperty(
813 name='Armature Levels',
814 description='Number of branching levels to make bones for, 0 is all levels',
815 min=0,
816 default=2, update=update_tree
818 boneStep: IntVectorProperty(
819 name='Bone Length',
820 description='Number of stem segments per bone',
821 min=1,
822 default=[1, 1, 1, 1],
823 size=4, update=update_tree
825 presetName: StringProperty(
826 name='Preset Name',
827 description='The name of the preset to be saved',
828 default='',
829 subtype='FILE_NAME', update=no_update_tree
831 limitImport: BoolProperty(
832 name='Limit Import',
833 description='Limited imported tree to 2 levels & no leaves for speed',
834 default=True, update=no_update_tree
836 overwrite: BoolProperty(
837 name='Overwrite',
838 description='When checked, overwrite existing preset files when saving',
839 default=False, update=no_update_tree
842 startCurv = FloatProperty(
843 name='Trunk Starting Angle',
844 description=('The angle between vertical and the starting direction'
845 'of the trunk'),
846 min=0.0,
847 max=360,
848 default=0.0, update=update_tree
852 @classmethod
853 def poll(cls, context):
854 return context.mode == 'OBJECT'
856 def draw(self, context):
857 layout = self.layout
859 # Branch specs
860 # layout.label(text='Tree Definition')
862 layout.prop(self, 'chooseSet')
864 if self.chooseSet == '0':
865 box = layout.box()
866 box.label(text="Geometry:")
867 box.prop(self, 'bevel')
869 row = box.row()
870 row.prop(self, 'bevelRes')
871 row.prop(self, 'resU')
873 box.prop(self, 'handleType')
874 box.prop(self, 'shape')
876 col = box.column()
877 col.prop(self, 'customShape')
879 row = box.row()
880 box.prop(self, 'shapeS')
881 box.prop(self, 'branchDist')
882 box.prop(self, 'nrings')
883 box.prop(self, 'seed')
885 box.label(text="Tree Scale:")
886 row = box.row()
887 row.prop(self, 'scale')
888 row.prop(self, 'scaleV')
890 # Here we create a dict of all the properties.
891 # Unfortunately as_keyword doesn't work with vector properties,
892 # so we need something custom. This is it
893 data = []
894 for a, b in (self.as_keywords(
895 ignore=("chooseSet", "presetName", "limitImport",
896 "do_update", "overwrite", "leafDupliObj"))).items():
897 # If the property is a vector property then add the slice to the list
898 try:
899 len(b)
900 data.append((a, b[:]))
901 # Otherwise, it is fine so just add it
902 except:
903 data.append((a, b))
904 # Create the dict from the list
905 data = dict(data)
907 row = box.row()
908 row.prop(self, 'presetName')
909 # Send the data dict and the file name to the exporter
910 row.operator('sapling.exportdata').data = repr([repr(data), self.presetName, self.overwrite])
911 row = box.row()
912 row.label(text=" ")
913 row.prop(self, 'overwrite')
914 row = box.row()
915 row.menu('SAPLING_MT_preset', text='Load Preset')
916 row.prop(self, 'limitImport')
918 elif self.chooseSet == '1':
919 box = layout.box()
920 box.label(text="Branch Radius:")
922 row = box.row()
923 row.prop(self, 'bevel')
924 row.prop(self, 'bevelRes')
926 box.prop(self, 'ratio')
927 row = box.row()
928 row.prop(self, 'scale0')
929 row.prop(self, 'scaleV0')
930 box.prop(self, 'ratioPower')
932 box.prop(self, 'minRadius')
933 box.prop(self, 'closeTip')
934 box.prop(self, 'rootFlare')
936 box.prop(self, 'autoTaper')
938 split = box.split()
939 col = split.column()
940 col.prop(self, 'taper')
941 col = split.column()
942 col.prop(self, 'radiusTweak')
944 elif self.chooseSet == '2':
945 box = layout.box()
946 box.label(text="Branch Splitting:")
947 box.prop(self, 'levels')
948 box.prop(self, 'baseSplits')
949 row = box.row()
950 row.prop(self, 'baseSize')
951 row.prop(self, 'baseSize_s')
952 box.prop(self, 'splitHeight')
953 box.prop(self, 'splitBias')
954 box.prop(self, 'splitByLen')
956 split = box.split()
958 col = split.column()
959 col.prop(self, 'branches')
960 col.prop(self, 'splitAngle')
961 col.prop(self, 'rotate')
962 col.prop(self, 'attractOut')
964 col = split.column()
965 col.prop(self, 'segSplits')
966 col.prop(self, 'splitAngleV')
967 col.prop(self, 'rotateV')
969 col.label(text="Branching Mode:")
970 col.prop(self, 'rMode')
972 box.column().prop(self, 'curveRes')
974 elif self.chooseSet == '3':
975 box = layout.box()
976 box.label(text="Branch Growth:")
978 box.prop(self, 'taperCrown')
980 split = box.split()
982 col = split.column()
983 col.prop(self, 'length')
984 col.prop(self, 'downAngle')
985 col.prop(self, 'curve')
986 col.prop(self, 'curveBack')
988 col = split.column()
989 col.prop(self, 'lengthV')
990 col.prop(self, 'downAngleV')
991 col.prop(self, 'curveV')
992 col.prop(self, 'attractUp')
994 box.prop(self, 'useOldDownAngle')
995 box.prop(self, 'useParentAngle')
997 elif self.chooseSet == '4':
998 box = layout.box()
999 box.label(text="Prune:")
1000 box.prop(self, 'prune')
1001 box.prop(self, 'pruneRatio')
1002 row = box.row()
1003 row.prop(self, 'pruneWidth')
1004 row.prop(self, 'pruneBase')
1005 box.prop(self, 'pruneWidthPeak')
1007 row = box.row()
1008 row.prop(self, 'prunePowerHigh')
1009 row.prop(self, 'prunePowerLow')
1011 elif self.chooseSet == '5':
1012 box = layout.box()
1013 box.label(text="Leaves:")
1014 box.prop(self, 'showLeaves')
1015 box.prop(self, 'leafShape')
1016 box.prop(self, 'leafDupliObj')
1017 box.prop(self, 'leaves')
1018 box.prop(self, 'leafDist')
1020 box.label(text="")
1021 row = box.row()
1022 row.prop(self, 'leafDownAngle')
1023 row.prop(self, 'leafDownAngleV')
1025 row = box.row()
1026 row.prop(self, 'leafRotate')
1027 row.prop(self, 'leafRotateV')
1028 box.label(text="")
1030 row = box.row()
1031 row.prop(self, 'leafScale')
1032 row.prop(self, 'leafScaleX')
1034 row = box.row()
1035 row.prop(self, 'leafScaleT')
1036 row.prop(self, 'leafScaleV')
1038 box.prop(self, 'horzLeaves')
1039 box.prop(self, 'leafangle')
1041 # box.label(text=" ")
1042 # box.prop(self, 'bend')
1044 elif self.chooseSet == '6':
1045 box = layout.box()
1046 box.label(text="Armature:")
1047 row = box.row()
1048 row.prop(self, 'useArm')
1049 box.prop(self, 'makeMesh')
1050 box.label(text="Armature Simplification:")
1051 box.prop(self, 'armLevels')
1052 box.prop(self, 'boneStep')
1054 elif self.chooseSet == '7':
1055 box = layout.box()
1056 box.label(text="Finalize All Other Settings First!")
1057 box.prop(self, 'armAnim')
1058 box.prop(self, 'leafAnim')
1059 box.prop(self, 'previewArm')
1060 box.prop(self, 'frameRate')
1061 box.prop(self, 'loopFrames')
1063 # row = box.row()
1064 # row.prop(self, 'windSpeed')
1065 # row.prop(self, 'windGust')
1067 box.label(text='Wind Settings:')
1068 box.prop(self, 'wind')
1069 row = box.row()
1070 row.prop(self, 'gust')
1071 row.prop(self, 'gustF')
1073 box.label(text='Leaf Wind Settings:')
1074 box.prop(self, 'af1')
1075 box.prop(self, 'af2')
1076 box.prop(self, 'af3')
1078 def execute(self, context):
1079 # Ensure the use of the global variables
1080 global settings, useSet
1081 start_time = time.time()
1083 # If we need to set the properties from a preset then do it here
1084 if useSet:
1085 for a, b in settings.items():
1086 setattr(self, a, b)
1087 if self.limitImport:
1088 setattr(self, 'levels', min(settings['levels'], 2))
1089 setattr(self, 'showLeaves', False)
1090 useSet = False
1091 if not self.do_update:
1092 return {'PASS_THROUGH'}
1093 utils.addTree(self)
1094 # cProfile.runctx("addTree(self)", globals(), locals())
1095 print("Tree creation in %0.1fs" % (time.time() - start_time))
1097 return {'FINISHED'}
1099 def invoke(self, context, event):
1100 bpy.ops.sapling.importdata(filename="callistemon.py")
1101 return self.execute(context)
1104 def menu_func(self, context):
1105 self.layout.operator(AddTree.bl_idname, text="Sapling Tree Gen", icon='CURVE_DATA')
1107 classes = (
1108 AddTree,
1109 PresetMenu,
1110 ImportData,
1111 ExportData,
1114 def register():
1115 from bpy.utils import register_class
1116 for cls in classes:
1117 register_class(cls)
1118 bpy.types.VIEW3D_MT_curve_add.append(menu_func)
1121 def unregister():
1122 from bpy.utils import unregister_class
1123 for cls in reversed(classes):
1124 unregister_class(cls)
1125 bpy.types.VIEW3D_MT_curve_add.remove(menu_func)
1128 if __name__ == "__main__":
1129 register()