reposicionado

This commit is contained in:
2025-04-04 04:30:44 +06:00
parent a7ac8826a0
commit 9524e73955
3 changed files with 305 additions and 368 deletions

435
Vegetation/PVPlantTree.ui Normal file
View File

@@ -0,0 +1,435 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>formRack</class>
<widget class="QDialog" name="formRack">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>222</width>
<height>280</height>
</rect>
</property>
<property name="windowTitle">
<string>Tracker:</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Canopy</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<property name="verticalSpacing">
<number>2</number>
</property>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Radius</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Divisiones</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="editCrownExpansion">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.050000000000000</double>
</property>
<property name="value">
<double>0.500000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Crown Expansion</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QDoubleSpinBox" name="editLeftUmbrellaEffect">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.050000000000000</double>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="editSpikiness">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.050000000000000</double>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Umbrella effect</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QSpinBox" name="editLeafCount">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>20</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Spikiness</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="editCanopyRadius">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="suffix">
<string> m</string>
</property>
<property name="minimum">
<double>0.010000000000000</double>
</property>
<property name="maximum">
<double>20.000000000000000</double>
</property>
<property name="value">
<double>1.500000000000000</double>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="editCanopyHeight">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="suffix">
<string> m</string>
</property>
<property name="minimum">
<double>0.010000000000000</double>
</property>
<property name="maximum">
<double>20.000000000000000</double>
</property>
<property name="value">
<double>4.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Trunk</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<property name="horizontalSpacing">
<number>5</number>
</property>
<property name="verticalSpacing">
<number>2</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Radius</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="editTrunkHeight">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="suffix">
<string> m</string>
</property>
<property name="maximum">
<double>50.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>2.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="editTrunkRadius">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="suffix">
<string> m</string>
</property>
<property name="minimum">
<double>0.010000000000000</double>
</property>
<property name="maximum">
<double>50.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>0.150000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Faces</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="editTrunkFaces">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="value">
<number>6</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View 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