Files
PVPlant/PVPlantTreeGenerator.py
2025-01-28 00:04:13 +01:00

369 lines
13 KiB
Python

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())