reposicionado
This commit is contained in:
@@ -1,368 +0,0 @@
|
|||||||
|
|
||||||
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())
|
|
||||||
|
|
||||||
305
Vegetation/PVPlantTreeGenerator.py
Normal file
305
Vegetation/PVPlantTreeGenerator.py
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
import ArchComponent
|
||||||
|
import FreeCAD
|
||||||
|
import Part
|
||||||
|
import random
|
||||||
|
from FreeCAD import Qt
|
||||||
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||||
|
|
||||||
|
try:
|
||||||
|
from scipy import spatial
|
||||||
|
has_scipy = True
|
||||||
|
except ImportError:
|
||||||
|
has_scipy = False
|
||||||
|
|
||||||
|
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 parametric tree object for architectural design"""
|
||||||
|
|
||||||
|
def __init__(self, obj):
|
||||||
|
ArchComponent.Component.__init__(self, obj)
|
||||||
|
self.obj = obj
|
||||||
|
self.setProperties(obj)
|
||||||
|
random.seed(42) # Semilla para resultados consistentes
|
||||||
|
|
||||||
|
def setProperties(self, obj):
|
||||||
|
"""Define y configura las propiedades del objeto"""
|
||||||
|
pl = obj.PropertiesList
|
||||||
|
|
||||||
|
# Propiedades de la copa
|
||||||
|
canopy_props = [
|
||||||
|
("CanopyHeight", "App::PropertyLength", "Altura total de la copa"),
|
||||||
|
("CanopyRadius", "App::PropertyLength", "Radio máximo de la copa"),
|
||||||
|
("Spikiness", "App::PropertyFloatConstraint", "Irregularidad de la superficie", (0.5, 0.0, 1.0, 0.05)),
|
||||||
|
(
|
||||||
|
"CrownExpansion", "App::PropertyFloatConstraint", "Expansión de la corona superior", (1.0, 0.0, 2.0, 0.05)),
|
||||||
|
("UmbrellaEffect", "App::PropertyFloatConstraint", "Efecto de dosel/paraguas", (0.0, 0.0, 1.0, 0.05)),
|
||||||
|
("LeafCount", "App::PropertyInteger", "Densidad de follaje (número de segmentos)")
|
||||||
|
]
|
||||||
|
|
||||||
|
for prop in canopy_props:
|
||||||
|
name, ptype, doc, *args = prop
|
||||||
|
if name not in pl:
|
||||||
|
if ptype == "App::PropertyFloatConstraint":
|
||||||
|
obj.addProperty(ptype, name, "Canopy", doc).__setattr__(name, args[0])
|
||||||
|
else:
|
||||||
|
obj.addProperty(ptype, name, "Canopy", doc)
|
||||||
|
# Valores por defecto
|
||||||
|
if name == "LeafCount":
|
||||||
|
setattr(obj, name, 20)
|
||||||
|
elif name in ["CanopyHeight", "CanopyRadius"]:
|
||||||
|
setattr(obj, name, 4000 if "Height" in name else 1500)
|
||||||
|
|
||||||
|
# Propiedades del tronco
|
||||||
|
trunk_props = [
|
||||||
|
("TrunkHeight", "App::PropertyLength", "Altura del tronco", 2000),
|
||||||
|
("TrunkRadius", "App::PropertyLength", "Radio base del tronco", 150),
|
||||||
|
("TrunkFaces", "App::PropertyInteger", "Caras del tronco", 6)
|
||||||
|
]
|
||||||
|
|
||||||
|
for prop in trunk_props:
|
||||||
|
name, ptype, doc, default = prop
|
||||||
|
if name not in pl:
|
||||||
|
obj.addProperty(ptype, name, "Trunk", doc)
|
||||||
|
setattr(obj, name, default)
|
||||||
|
|
||||||
|
# Propiedades base
|
||||||
|
if "Type" not in pl:
|
||||||
|
obj.addProperty("App::PropertyString", "Type", "Base", "Tipo de objeto").Type = "Vegetable-Tree"
|
||||||
|
obj.setEditorMode("Type", 1) # Hacerla de solo lectura
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""Actualiza la forma cuando cambian propiedades"""
|
||||||
|
if prop in ["CanopyHeight", "CanopyRadius", "Spikiness", "CrownExpansion",
|
||||||
|
"UmbrellaEffect", "LeafCount", "TrunkHeight", "TrunkRadius", "TrunkFaces"]:
|
||||||
|
self.execute(obj)
|
||||||
|
|
||||||
|
def createTrunk(self, obj):
|
||||||
|
"""Crea la geometría del tronco usando un loft"""
|
||||||
|
try:
|
||||||
|
# Calcula dimensiones proporcionales
|
||||||
|
base_radius = obj.TrunkRadius.Value
|
||||||
|
top_radius = base_radius * 0.8
|
||||||
|
height = obj.TrunkHeight.Value
|
||||||
|
|
||||||
|
# Crea tres perfiles circulares
|
||||||
|
profiles = []
|
||||||
|
for z, radius in [(0, base_radius),
|
||||||
|
(height / 3, base_radius),
|
||||||
|
(height, top_radius)]:
|
||||||
|
circle = Part.makeCircle(radius, FreeCAD.Vector(0, 0, z))
|
||||||
|
profiles.append(Part.Wire([circle]))
|
||||||
|
|
||||||
|
return Part.makeLoft(profiles, True, True)
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintError(f"Error creando tronco: {str(e)}\n")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def createCanopy(self, obj):
|
||||||
|
"""Genera la forma de la copa usando una envoltura convexa"""
|
||||||
|
if not has_scipy:
|
||||||
|
FreeCAD.Console.PrintError("Scipy no está instalado. No se puede generar la copa.\n")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Configuración inicial
|
||||||
|
n_segments = max(3, obj.LeafCount) # Mínimo 3 segmentos
|
||||||
|
radius = obj.CanopyRadius.Value
|
||||||
|
height = obj.CanopyHeight.Value
|
||||||
|
|
||||||
|
# Genera puntos distribuidos esféricamente con ruido
|
||||||
|
points = []
|
||||||
|
for _ in range(n_segments * 10): # 10 puntos por segmento
|
||||||
|
theta = random.uniform(0, 2 * math.pi)
|
||||||
|
phi = math.acos(random.uniform(-1, 1))
|
||||||
|
|
||||||
|
# Aplica parámetros de forma
|
||||||
|
r = radius * (1 - obj.Spikiness * random.random())
|
||||||
|
x = r * math.sin(phi) * math.cos(theta)
|
||||||
|
y = r * math.sin(phi) * math.sin(theta)
|
||||||
|
z = height * (0.5 + 0.5 * math.cos(phi)) # Distribución vertical
|
||||||
|
|
||||||
|
# Aplica efectos de forma
|
||||||
|
z *= (1 - obj.UmbrellaEffect)
|
||||||
|
if z > height / 2:
|
||||||
|
x *= obj.CrownExpansion
|
||||||
|
y *= obj.CrownExpansion
|
||||||
|
|
||||||
|
points.append(FreeCAD.Vector(x, y, z))
|
||||||
|
|
||||||
|
# Crea la envoltura convexa
|
||||||
|
hull = spatial.ConvexHull([(p.x, p.y, p.z) for p in points])
|
||||||
|
faces = []
|
||||||
|
for simplex in hull.simplices:
|
||||||
|
triangle = [points[i] for i in simplex]
|
||||||
|
faces.append(Part.Face(Part.makePolygon(triangle + [triangle[0]])))
|
||||||
|
|
||||||
|
return Part.Compound(faces)
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintError(f"Error creando copa: {str(e)}\n")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def execute(self, obj):
|
||||||
|
"""Ensambla el objeto final"""
|
||||||
|
try:
|
||||||
|
# Crea componentes
|
||||||
|
trunk = self.createTrunk(obj)
|
||||||
|
canopy = self.createCanopy(obj)
|
||||||
|
|
||||||
|
# Verifica componentes válidos
|
||||||
|
if not trunk or not canopy:
|
||||||
|
raise ValueError("Error en la generación de componentes")
|
||||||
|
|
||||||
|
# Posiciona la copa sobre el tronco
|
||||||
|
canopy_placement = FreeCAD.Placement()
|
||||||
|
canopy_placement.Base.z = obj.TrunkHeight.Value
|
||||||
|
canopy.Placement = canopy_placement
|
||||||
|
|
||||||
|
# Combina las formas
|
||||||
|
compound = Part.Compound([trunk, canopy])
|
||||||
|
obj.Shape = compound
|
||||||
|
|
||||||
|
# Configura apariencia
|
||||||
|
if obj.ViewObject:
|
||||||
|
obj.ViewObject.DiffuseColor = ([(0.35, 0.2, 0.05)] * len(trunk.Faces) +
|
||||||
|
[(0.1, 0.6, 0.2)] * len(canopy.Faces)) # Color copa
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintError(f"Error al ejecutar: {str(e)}\n")
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
Reference in New Issue
Block a user