import math import ArchComponent import FreeCAD if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore, QtGui from DraftTools import translate from PySide.QtCore import QT_TRANSLATE_NOOP import Part import os else: # \cond def translate(ctxt, txt): return txt def QT_TRANSLATE_NOOP(ctxt, txt): return txt # \endcond __title__ = "FreeCAD Fixed Rack" __author__ = "Javier Braña" __url__ = "http://www.sogos-solar.com" __dir__ = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", "PVPlant") DirResources = os.path.join(__dir__, "Resources") DirIcons = os.path.join(DirResources, "Icons") DirImages = os.path.join(DirResources, "Images") def makeTree(): obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Tree") Tree(obj) ViewProviderTree(obj.ViewObject) FreeCAD.ActiveDocument.recompute() try: folder = FreeCAD.ActiveDocument.Vegetation except: folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Vegetation') folder.Label = "Vegetation" folder.addObject(obj) return obj class Tree(ArchComponent.Component): """ A Shadow Tree Obcject """ def __init__(self, obj): # Definición de variables: ArchComponent.Component.__init__(self, obj) self.obj = obj self.setProperties(obj) def setProperties(self, obj): # Definicion de Propiedades: pl = obj.PropertiesList # CANOPY: --------------------------------------------------------- if not ("CanopyHeight" in pl): obj.addProperty("App::PropertyLength", "CanopyHeight", "Canopy", QT_TRANSLATE_NOOP("App::Property", "The height of self object") ).CanopyHeight = 4000 if not ("CanopyRadius" in pl): obj.addProperty("App::PropertyLength", "CanopyRadius", "Canopy", QT_TRANSLATE_NOOP("App::Property", "The height of self object") ).CanopyRadius = 1500 if not ("Spikiness" in pl): obj.addProperty("App::PropertyFloatConstraint", "Spikiness", "Canopy", QT_TRANSLATE_NOOP("App::Property", "The height of self object") ).Spikiness = (0.5, 0.0, 1.0, 0.05) # (Default, Start, Finish, Step) ''' if not ("Lumpiness" in pl): obj.addProperty("App::PropertyFloatConstraint", "Lumpiness", "Canopy", QT_TRANSLATE_NOOP("App::Property", "The height of self object") ).Lumpiness = (0.0, 0.0, 1.0, 0.05) #(Default, Start, Finish, Step)''' if not ("CrownExpansion" in pl): obj.addProperty("App::PropertyFloatConstraint", "CrownExpansion", "Canopy", QT_TRANSLATE_NOOP("App::Property", "The height of self object") ).CrownExpansion = (1.0, 0.0, 2.0, 0.05) # (Default, Start, Finish, Step) if not ("UmbrellaEffect" in pl): obj.addProperty("App::PropertyFloatConstraint", "UmbrellaEffect", "Canopy", QT_TRANSLATE_NOOP("App::Property", "The height of self object") ).UmbrellaEffect = (0.0, 0.0, 1.0, 0.05) # (Default, Start, Finish, Step) if not ("LeafCount" in pl): obj.addProperty("App::PropertyQuantity", "LeafCount", "Canopy", QT_TRANSLATE_NOOP("App::Property", "The height of self object") ).LeafCount = 20 # TRUNK: ------------------------------------------------------------------------------------------------------ if not ("TrunkHeight" in pl): obj.addProperty("App::PropertyLength", "TrunkHeight", "Trunk", QT_TRANSLATE_NOOP("App::Property", "The height of self object") ).TrunkHeight = 2000 if not ("TrunkRadius" in pl): obj.addProperty("App::PropertyLength", "TrunkRadius", "Trunk", QT_TRANSLATE_NOOP("App::Property", "The height of self object") ).TrunkRadius = 150 if not ("TrunkFaces" in pl): obj.addProperty("App::PropertyQuantity", "TrunkFaces", "Trunk", QT_TRANSLATE_NOOP("App::Property", "The height of self object") ).TrunkFaces = 6 if not ("Type" in pl): obj.addProperty("App::PropertyString", "Type", "Base", "Type").Type = "Vegetable-Tree" obj.setEditorMode("Type", 1) self.Type = obj.Type obj.Proxy = self obj.IfcType = "Shading Device" obj.setEditorMode("IfcType", 1) def onDocumentRestored(self, obj): ArchComponent.Component.onDocumentRestored(self, obj) self.setProperties(obj) def onChanged(self, obj, prop): '''if prop in ["CanopyHeight", "CanopyHeight", "Spikiness", "CrownExpansion", "UmbrellaEffect", "LeafCount"]: self.canopy = self.createCanopy(obj) if prop in ["TrunkHeight", "TrunkRadius", "TrunkFaces"]: self.trunk = self.createTrunk(obj)''' def createTrunk(self, obj): import Part angle = (math.pi * 2) / obj.TrunkFaces.Value delta = obj.TrunkRadius.Value pts = [FreeCAD.Vector(delta, 0, 0)] for i in range(int(obj.TrunkFaces.Value) - 1): ang = (i + 1) * angle pts.append(FreeCAD.Vector(delta * math.cos(ang), delta * math.sin(ang), 0)) pts.append(pts[0]) p1 = Part.makePolygon(pts) p0 = p1.makeOffset2D(90, 2, False, False, True) p2 = p1.makeOffset2D(-50, 2, False, False, True) p0.Placement.Base.z = -150 p1.Placement.Base.z = 150 p2.Placement.Base.z = obj.TrunkHeight.Value - 250 return Part.makeLoft([p0, p1, p2], True, True, False) def createCanopy(self, obj): import Part import random import Mesh import numpy as np ncircles = int(obj.LeafCount.Value) if ncircles % 2 == 0: ncircles += 1 half_ncircles = int(ncircles / 2) ncirclesumbrella = int(half_ncircles/2) ncirclestop = ncircles - ncirclesumbrella # 1. Create circles to define the sphere circles = [] dist = 2 * obj.CanopyRadius.Value / (ncircles - 1) margin = dist * 0.01 '''for i in range(half_ncircles + 1): if i > 0: d = (obj.CanopyRadius.Value - dist * i) else: d = obj.CanopyRadius.Value - margin r = (obj.CanopyRadius.Value ** 2 - d ** 2) ** 0.5 c = Part.makeCircle(r) circles.append(c) ctmp = [c.copy() for c in circles] ctmp.pop() ctmp.reverse() circles.extend(ctmp)''' d = - obj.CanopyRadius.Value - dist b = (obj.CanopyRadius.Value ** 2 - (dist * (ncirclesumbrella - 1)) ** 2) ** 0.5 for i in range(ncircles): d += dist r = (obj.CanopyRadius.Value ** 2 - d ** 2) ** 0.5 if i > ncirclesumbrella: if obj.CrownExpansion < 1: r = r * obj.CrownExpansion if r == 0: r = obj.CanopyRadius.Value * 0.01 c = Part.makeCircle(r) circles.append(c) # 2. Place circles dist = obj.CanopyHeight.Value / ncircles z = 0 #zmax = dist * half_ncircles for idx in range(1, half_ncircles): z += dist circles[idx].Placement.Base.z = (1 - obj.UmbrellaEffect) * z #c.Placement.Base.z = obj.UmbrellaEffect * (zmax - z) + z dist1 = (obj.CanopyHeight.Value - z) / (half_ncircles + 1) for idx in range(half_ncircles, ncircles): c = circles[idx] z += dist1 c.Placement.Base.z = z # 3. noise generator pts = [] val = (dist / 2 - margin) * obj.Spikiness for c in circles: tmppts = c.discretize(ncircles) for j in range(len(tmppts)): point = tmppts[j] point.x += random.uniform(-val, val) point.y += random.uniform(-val, val) point.z += random.uniform(-val, val) pts.append(point) # 4. generate the mesh / solid from scipy import spatial as sp_spatial pts = np.array(pts) hull = sp_spatial.ConvexHull(pts) indices = hull.simplices faces = pts[indices] mesh = Mesh.Mesh(faces.tolist()) if len(mesh.Facets) == 0: return None mesh.harmonizeNormals() '''if mesh.Facets[0].Normal.z < 0: mesh.flipNormals()''' shape = Part.Shape() shape.makeShapeFromMesh(mesh.Topology, 0.1) return Part.makeSolid(shape) def execute(self, obj): pl = obj.Placement trunk = self.createTrunk(obj) canopy = self.createCanopy(obj) canopy.Placement.Base.z = obj.TrunkHeight.Value - 250 # - obj.CanopyRadius.Value / 2 obj.Shape = Part.makeCompound([trunk, canopy]) obj.Placement = pl color = [(0.2510, 0.1255, 0.0)] * len(trunk.Faces) color.extend([(0.0, 0.3922, 0.0)] * len(canopy.Faces)) obj.ViewObject.DiffuseColor = color class ViewProviderTree(ArchComponent.ViewProviderComponent): "A View Provider for the Pipe object" def __init__(self, vobj): ArchComponent.ViewProviderComponent.__init__(self, vobj) def getIcon(self): return str(os.path.join(DirIcons, "tree(1).svg")) class TreeTaskPanel(QtGui.QWidget): def __init__(self, obj=None): QtGui.QWidget.__init__(self) self.obj = obj if self.obj is None: self.obj = makeTree() self.form = FreeCADGui.PySideUic.loadUi(__dir__ + "/PVPlantTree.ui") self.layout = QtGui.QHBoxLayout(self) self.layout.setContentsMargins(4, 4, 4, 4) self.layout.addWidget(self.form) self.form.editCanopyHeight.valueChanged.connect(self.Canopy) self.form.editCanopyRadius.valueChanged.connect(self.Canopy) self.form.editSpikiness.valueChanged.connect(self.Canopy) self.form.editCrownExpansion.valueChanged.connect(self.Canopy) self.form.editLeftUmbrellaEffect.valueChanged.connect(self.Canopy) self.form.editLeafCount.valueChanged.connect(self.Canopy) def Canopy(self): self.obj.CanopyHeight = FreeCAD.Units.Quantity(self.form.editCanopyHeight.text()).Value self.obj.CanopyRadius = FreeCAD.Units.Quantity(self.form.editCanopyRadius.text()).Value self.obj.Spikiness = self.form.editSpikiness.value() self.obj.CrownExpansion = self.form.editCrownExpansion.value() self.obj.UmbrellaEffect = self.form.editLeftUmbrellaEffect.value() self.obj.LeafCount = self.form.editLeafCount.value() FreeCAD.ActiveDocument.recompute() def accept(self): FreeCADGui.Control.closeDialog() return True def reject(self): FreeCAD.ActiveDocument.removeObject(self.obj.Name) FreeCADGui.Control.closeDialog() return True class _CommandTree: "the PVPlant Tree command definition" def GetResources(self): return {'Pixmap': str(os.path.join(DirIcons, "tree(1).svg")), 'MenuText': QtCore.QT_TRANSLATE_NOOP("PVPlantTree", "Tree"), 'Accel': "S, T", 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PVPlanTree", "Creates a Tree object from setup dialog.")} def IsActive(self): return not FreeCAD.ActiveDocument is None def Activated(self): import draftguitools.gui_trackers as DraftTrackers self.tree = makeTree() FreeCADGui.Snapper.getPoint(callback=self.getPoint, movecallback=self.mousemove, extradlg=self.taskbox(), title="Position of the tree:") def getPoint(self, point=None, obj=None): self.tree.Placement.Base = point FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() self.tracker.finalize() def mousemove(self, pt, snapInfo): self.tree.Placement.Base = pt def taskbox(self): self.form = TreeTaskPanel(self.tree) return self.form if FreeCAD.GuiUp: FreeCADGui.addCommand('PVPlantTree', _CommandTree())