2025-01-28 00:04:13 +01:00
# /**********************************************************************
# * *
# * Copyright (c) 2021 Javier Braña <javier.branagutierrez@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify*
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307*
# * USA *
# * *
# ***********************************************************************
import FreeCAD , Draft , ArchCommands , math , datetime
import ArchSite
if FreeCAD . GuiUp :
import FreeCADGui
from DraftTools import translate
from PySide . QtCore import QT_TRANSLATE_NOOP
from pivy import coin
else :
# \cond
def translate ( ctxt , txt ) :
return txt
def QT_TRANSLATE_NOOP ( ctxt , txt ) :
return txt
# \endcond
import os
from PVPlantResources import DirIcons as DirIcons
__title__ = " FreeCAD Site "
__author__ = " "
__url__ = " http://www.freecadweb.org "
zone_list = [ " Z1 " , " Z2 " , " Z3 " , " Z4 " , " Z5 " , " Z6 " , " Z7 " , " Z8 " , " Z9 " , " Z10 " , " Z11 " , " Z12 " ,
" Z13 " , " Z14 " , " Z15 " , " Z16 " , " Z17 " , " Z18 " , " Z19 " , " Z20 " , " Z21 " , " Z22 " , " Z23 " , " Z24 " ,
" Z25 " , " Z26 " , " Z27 " , " Z28 " , " Z29 " , " Z30 " , " Z31 " , " Z32 " , " Z33 " , " Z34 " , " Z35 " , " Z36 " ,
" Z37 " , " Z38 " , " Z39 " , " Z40 " , " Z41 " , " Z42 " , " Z43 " , " Z44 " , " Z45 " , " Z46 " , " Z47 " , " Z48 " ,
" Z49 " , " Z50 " , " Z51 " , " Z52 " , " Z53 " , " Z54 " , " Z55 " , " Z56 " , " Z57 " , " Z58 " , " Z59 " , " Z60 " ]
def get ( origin = FreeCAD . Vector ( 0 , 0 , 0 ) , create = False ) :
""" Find the existing Site object """
# Return an existing instance of the same name, if found.
obj = FreeCAD . ActiveDocument . getObject ( ' Site ' )
if obj :
if obj . Origin == FreeCAD . Vector ( 0 , 0 , 0 ) :
obj . Origin = origin
return obj
if not obj and create :
obj = makePVPlantSite ( )
return obj
def PartToWire ( part ) :
import Part , Draft
PointList = [ ]
edges = Part . __sortEdges__ ( part . Shape . Edges )
for edge in edges :
PointList . append ( edge . Vertexes [ 0 ] . Point )
PointList . append ( edges [ - 1 ] . Vertexes [ - 1 ] . Point )
Draft . makeWire ( PointList , closed = True , face = None , support = None )
def projectWireOnMesh ( Boundary , Mesh ) :
import Draft
use = True
if use :
import MeshPart as mp
plist = mp . projectShapeOnMesh ( Boundary . Shape , Mesh , FreeCAD . Vector ( 0 , 0 , 1 ) )
PointList = [ ]
for pl in plist :
PointList + = pl
Draft . makeWire ( PointList , closed = True , face = None , support = None )
FreeCAD . activeDocument ( ) . recompute ( )
else : ## posible código:
import MeshPart
CopyMesh = Mesh . copy ( )
Base = CopyMesh . Placement . Base
CopyMesh . Placement . move ( Base . negative ( ) )
CopyShape = Boundary . Shape . copy ( )
CopyShape . Placement . move ( Base . negative ( ) )
Vec = CopyShape . Edge1 . Vertexes [ 0 ] . Point - CopyShape . Edge1 . Vertexes [ 1 ] . Point
Vec . x , Vec . y = - ( Vec . y ) , Vec . x
Section = CopyMesh . crossSections ( [ ( CopyShape . Edge1 . Vertexes [ 0 ] . Point , Vec ) ] , 0.000001 )
print ( Section )
for i in Section [ 0 ] :
Pwire = Draft . makeWire ( i )
# Pwire.Placement.move(Base)
##SectionGroup.addObject(Pwire)
FreeCAD . ActiveDocument . recompute ( )
def makePVPlantSite ( ) :
def createGroup ( father , groupname , type = None ) :
group = FreeCAD . ActiveDocument . addObject ( " App::DocumentObjectGroup " , groupname )
group . Label = groupname
father . addObject ( group )
return group
obj = FreeCAD . ActiveDocument . addObject ( " Part::FeaturePython " , " Site " )
_PVPlantSite ( obj )
if FreeCAD . GuiUp :
_ViewProviderSite ( obj . ViewObject )
group = createGroup ( obj , " CivilGroup " )
group1 = createGroup ( group , " Areas " )
createGroup ( group1 , " Boundaries " )
createGroup ( group1 , " CadastralPlots " )
createGroup ( group1 , " Exclusions " )
createGroup ( group1 , " FrameZones " )
createGroup ( group1 , " Offsets " )
createGroup ( group1 , " Plots " )
createGroup ( group , " Drains " )
createGroup ( group , " Earthworks " )
createGroup ( group , " Fences " )
createGroup ( group , " Foundations " )
createGroup ( group , " Pads " )
createGroup ( group , " Points " )
createGroup ( group , " Roads " )
createGroup ( group , " Trenches " )
group = createGroup ( obj , " ElectricalGroup " )
createGroup ( group , " StringInverters " )
createGroup ( group , " CentralInverter " )
group1 = createGroup ( group , " AC " )
createGroup ( group1 , " CableAC " )
group1 = createGroup ( group , " DC " )
createGroup ( group1 , " CableDC " )
createGroup ( group1 , " StringsSetup " )
createGroup ( group1 , " Strings " )
createGroup ( group1 , " StringsBoxes " )
group = createGroup ( obj , " MechanicalGroup " )
createGroup ( group , " FramesSetups " )
createGroup ( group , " Frames " )
group = createGroup ( obj , " Environment " )
createGroup ( group , " Vegetation " )
return obj
def makeSolarDiagram ( longitude , latitude , scale = 1 , complete = False , tz = None ) :
""" makeSolarDiagram(longitude,latitude,[scale,complete,tz]):
returns a solar diagram as a pivy node . If complete is
True , the 12 months are drawn . Tz is the timezone related to
UTC ( ex : - 3 = UTC - 3 ) """
oldversion = False
ladybug = False
try :
import ladybug
from ladybug import location
from ladybug import sunpath
except :
# TODO - remove pysolar dependency
# FreeCAD.Console.PrintWarning("Ladybug module not found, using pysolar instead. Warning, this will be deprecated in the future\n")
ladybug = False
try :
import pysolar
except :
try :
import Pysolar as pysolar
except :
FreeCAD . Console . PrintError ( " The pysolar module was not found. Unable to generate solar diagrams \n " )
return None
else :
oldversion = True
if tz :
tz = datetime . timezone ( datetime . timedelta ( hours = - 3 ) )
else :
tz = datetime . timezone . utc
else :
loc = ladybug . location . Location ( latitude = latitude , longitude = longitude , time_zone = tz )
sunpath = ladybug . sunpath . Sunpath . from_location ( loc )
from pivy import coin
if not scale :
return None
circles = [ ]
sunpaths = [ ]
hourpaths = [ ]
circlepos = [ ]
hourpos = [ ]
# build the base circle + number positions
import Part
for i in range ( 1 , 9 ) :
circles . append ( Part . makeCircle ( scale * ( i / 8.0 ) ) )
for ad in range ( 0 , 360 , 15 ) :
a = math . radians ( ad )
p1 = FreeCAD . Vector ( math . cos ( a ) * scale , math . sin ( a ) * scale , 0 )
p2 = FreeCAD . Vector ( math . cos ( a ) * scale * 0.125 , math . sin ( a ) * scale * 0.125 , 0 )
p3 = FreeCAD . Vector ( math . cos ( a ) * scale * 1.08 , math . sin ( a ) * scale * 1.08 , 0 )
circles . append ( Part . LineSegment ( p1 , p2 ) . toShape ( ) )
circlepos . append ( ( ad , p3 ) )
# build the sun curves at solstices and equinoxe
year = datetime . datetime . now ( ) . year
hpts = [ [ ] for i in range ( 24 ) ]
m = [ ( 6 , 21 ) , ( 7 , 21 ) , ( 8 , 21 ) , ( 9 , 21 ) , ( 10 , 21 ) , ( 11 , 21 ) , ( 12 , 21 ) ]
if complete :
m . extend ( [ ( 1 , 21 ) , ( 2 , 21 ) , ( 3 , 21 ) , ( 4 , 21 ) , ( 5 , 21 ) ] )
for i , d in enumerate ( m ) :
pts = [ ]
for h in range ( 24 ) :
if ladybug :
sun = sunpath . calculate_sun ( month = d [ 0 ] , day = d [ 1 ] , hour = h )
alt = math . radians ( sun . altitude )
az = 90 + sun . azimuth
elif oldversion :
dt = datetime . datetime ( year , d [ 0 ] , d [ 1 ] , h )
alt = math . radians ( pysolar . solar . GetAltitudeFast ( latitude , longitude , dt ) )
az = pysolar . solar . GetAzimuth ( latitude , longitude , dt )
az = - 90 + az # pysolar's zero is south, ours is X direction
else :
dt = datetime . datetime ( year , d [ 0 ] , d [ 1 ] , h , tzinfo = tz )
alt = math . radians ( pysolar . solar . get_altitude_fast ( latitude , longitude , dt ) )
az = pysolar . solar . get_azimuth ( latitude , longitude , dt )
az = 90 + az # pysolar's zero is north, ours is X direction
if az < 0 :
az = 360 + az
az = math . radians ( az )
zc = math . sin ( alt ) * scale
ic = math . cos ( alt ) * scale
xc = math . cos ( az ) * ic
yc = math . sin ( az ) * ic
p = FreeCAD . Vector ( xc , yc , zc )
pts . append ( p )
hpts [ h ] . append ( p )
if i in [ 0 , 6 ] :
ep = FreeCAD . Vector ( p )
ep . multiply ( 1.08 )
if ep . z > = 0 :
if not oldversion :
h = 24 - h # not sure why this is needed now... But it is.
if h == 12 :
if i == 0 :
h = " SUMMER "
else :
h = " WINTER "
if latitude < 0 :
if h == " SUMMER " :
h = " WINTER "
else :
h = " SUMMER "
hourpos . append ( ( h , ep ) )
if i < 7 :
sunpaths . append ( Part . makePolygon ( pts ) )
for h in hpts :
if complete :
h . append ( h [ 0 ] )
hourpaths . append ( Part . makePolygon ( h ) )
# cut underground lines
sz = 2.1 * scale
cube = Part . makeBox ( sz , sz , sz )
cube . translate ( FreeCAD . Vector ( - sz / 2 , - sz / 2 , - sz ) )
sunpaths = [ sp . cut ( cube ) for sp in sunpaths ]
hourpaths = [ hp . cut ( cube ) for hp in hourpaths ]
# build nodes
ts = 0.005 * scale # text scale
mastersep = coin . SoSeparator ( )
circlesep = coin . SoSeparator ( )
numsep = coin . SoSeparator ( )
pathsep = coin . SoSeparator ( )
hoursep = coin . SoSeparator ( )
hournumsep = coin . SoSeparator ( )
mastersep . addChild ( circlesep )
mastersep . addChild ( numsep )
mastersep . addChild ( pathsep )
mastersep . addChild ( hoursep )
for item in circles :
circlesep . addChild ( toNode ( item ) )
for item in sunpaths :
for w in item . Edges :
pathsep . addChild ( toNode ( w ) )
for item in hourpaths :
for w in item . Edges :
hoursep . addChild ( toNode ( w ) )
for p in circlepos :
text = coin . SoText2 ( )
s = p [ 0 ] - 90
s = - s
if s > 360 :
s = s - 360
if s < 0 :
s = 360 + s
if s == 0 :
s = " N "
elif s == 90 :
s = " E "
elif s == 180 :
s = " S "
elif s == 270 :
s = " W "
else :
s = str ( s )
text . string = s
text . justification = coin . SoText2 . CENTER
coords = coin . SoTransform ( )
coords . translation . setValue ( [ p [ 1 ] . x , p [ 1 ] . y , p [ 1 ] . z ] )
coords . scaleFactor . setValue ( [ ts , ts , ts ] )
item = coin . SoSeparator ( )
item . addChild ( coords )
item . addChild ( text )
numsep . addChild ( item )
for p in hourpos :
text = coin . SoText2 ( )
s = str ( p [ 0 ] )
text . string = s
text . justification = coin . SoText2 . CENTER
coords = coin . SoTransform ( )
coords . translation . setValue ( [ p [ 1 ] . x , p [ 1 ] . y , p [ 1 ] . z ] )
coords . scaleFactor . setValue ( [ ts , ts , ts ] )
item = coin . SoSeparator ( )
item . addChild ( coords )
item . addChild ( text )
numsep . addChild ( item )
return mastersep
def makeWindRose ( epwfile , scale = 1 , sectors = 24 ) :
""" makeWindRose(site,sectors):
returns a wind rose diagram as a pivy node """
try :
import ladybug
from ladybug import epw
except :
FreeCAD . Console . PrintError ( " The ladybug module was not found. Unable to generate solar diagrams \n " )
return None
if not epwfile :
FreeCAD . Console . PrintWarning ( " No EPW file, unable to generate wind rose. \n " )
return None
epw_data = ladybug . epw . EPW ( epwfile )
baseangle = 360 / sectors
sectorangles = [ i * baseangle for i in range ( sectors ) ] # the divider angles between each sector
basebissect = baseangle / 2
angles = [ basebissect ] # build a list of central direction for each sector
for i in range ( 1 , sectors ) :
angles . append ( angles [ - 1 ] + baseangle )
windsbysector = [ 0 for i in range ( sectors ) ] # prepare a holder for values for each sector
for hour in epw_data . wind_direction :
sector = min ( angles , key = lambda x : abs ( x - hour ) ) # find the closest sector angle
sectorindex = angles . index ( sector )
windsbysector [ sectorindex ] = windsbysector [ sectorindex ] + 1
maxwind = max ( windsbysector )
windsbysector = [ wind / maxwind for wind in windsbysector ] # normalize
vectors = [ ] # create 3D vectors
dividers = [ ]
for i in range ( sectors ) :
angle = math . radians ( 90 + angles [ i ] )
x = math . cos ( angle ) * windsbysector [ i ] * scale
y = math . sin ( angle ) * windsbysector [ i ] * scale
vectors . append ( FreeCAD . Vector ( x , y , 0 ) )
secangle = math . radians ( 90 + sectorangles [ i ] )
x = math . cos ( secangle ) * scale
y = math . sin ( secangle ) * scale
dividers . append ( FreeCAD . Vector ( x , y , 0 ) )
vectors . append ( vectors [ 0 ] )
# build coin node
import Part
from pivy import coin
masternode = coin . SoSeparator ( )
for r in ( 0.25 , 0.5 , 0.75 , 1.0 ) :
c = Part . makeCircle ( r * scale )
masternode . addChild ( toNode ( c ) )
for divider in dividers :
l = Part . makeLine ( FreeCAD . Vector ( ) , divider )
masternode . addChild ( toNode ( l ) )
ds = coin . SoDrawStyle ( )
ds . lineWidth = 2.0
masternode . addChild ( ds )
d = Part . makePolygon ( vectors )
masternode . addChild ( toNode ( d ) )
return masternode
# Values in mm
COMPASS_POINTER_LENGTH = 1000
COMPASS_POINTER_WIDTH = 100
class Compass ( object ) :
def __init__ ( self ) :
self . rootNode = self . setupCoin ( )
def show ( self ) :
from pivy import coin
self . compassswitch . whichChild = coin . SO_SWITCH_ALL
def hide ( self ) :
from pivy import coin
self . compassswitch . whichChild = coin . SO_SWITCH_NONE
def rotate ( self , angleInDegrees ) :
from pivy import coin
self . transform . rotation . setValue (
coin . SbVec3f ( 0 , 0 , 1 ) , math . radians ( angleInDegrees ) )
def locate ( self , x , y , z ) :
from pivy import coin
self . transform . translation . setValue ( x , y , z )
def scale ( self , area ) :
from pivy import coin
scale = round ( max ( math . sqrt ( area . getValueAs ( " m^2 " ) . Value ) / 10 , 1 ) )
self . transform . scaleFactor . setValue ( coin . SbVec3f ( scale , scale , 1 ) )
def setupCoin ( self ) :
from pivy import coin
compasssep = coin . SoSeparator ( )
self . transform = coin . SoTransform ( )
darkNorthMaterial = coin . SoMaterial ( )
darkNorthMaterial . diffuseColor . set1Value (
0 , 0.5 , 0 , 0 ) # north dark color
lightNorthMaterial = coin . SoMaterial ( )
lightNorthMaterial . diffuseColor . set1Value (
0 , 0.9 , 0 , 0 ) # north light color
darkGreyMaterial = coin . SoMaterial ( )
darkGreyMaterial . diffuseColor . set1Value ( 0 , 0.9 , 0.9 , 0.9 ) # dark color
lightGreyMaterial = coin . SoMaterial ( )
lightGreyMaterial . diffuseColor . set1Value ( 0 , 0.5 , 0.5 , 0.5 ) # light color
coords = self . buildCoordinates ( )
# coordIndex = [0, 1, 2, -1, 2, 3, 0, -1]
lightColorFaceset = coin . SoIndexedFaceSet ( )
lightColorCoordinateIndex = [ 4 , 5 , 6 , - 1 , 8 , 9 , 10 , - 1 , 12 , 13 , 14 , - 1 ]
lightColorFaceset . coordIndex . setValues (
0 , len ( lightColorCoordinateIndex ) , lightColorCoordinateIndex )
darkColorFaceset = coin . SoIndexedFaceSet ( )
darkColorCoordinateIndex = [ 6 , 7 , 4 , - 1 , 10 , 11 , 8 , - 1 , 14 , 15 , 12 , - 1 ]
darkColorFaceset . coordIndex . setValues (
0 , len ( darkColorCoordinateIndex ) , darkColorCoordinateIndex )
lightNorthFaceset = coin . SoIndexedFaceSet ( )
lightNorthCoordinateIndex = [ 2 , 3 , 0 , - 1 ]
lightNorthFaceset . coordIndex . setValues (
0 , len ( lightNorthCoordinateIndex ) , lightNorthCoordinateIndex )
darkNorthFaceset = coin . SoIndexedFaceSet ( )
darkNorthCoordinateIndex = [ 0 , 1 , 2 , - 1 ]
darkNorthFaceset . coordIndex . setValues (
0 , len ( darkNorthCoordinateIndex ) , darkNorthCoordinateIndex )
self . compassswitch = coin . SoSwitch ( )
self . compassswitch . whichChild = coin . SO_SWITCH_NONE
self . compassswitch . addChild ( compasssep )
lightGreySeparator = coin . SoSeparator ( )
lightGreySeparator . addChild ( lightGreyMaterial )
lightGreySeparator . addChild ( lightColorFaceset )
darkGreySeparator = coin . SoSeparator ( )
darkGreySeparator . addChild ( darkGreyMaterial )
darkGreySeparator . addChild ( darkColorFaceset )
lightNorthSeparator = coin . SoSeparator ( )
lightNorthSeparator . addChild ( lightNorthMaterial )
lightNorthSeparator . addChild ( lightNorthFaceset )
darkNorthSeparator = coin . SoSeparator ( )
darkNorthSeparator . addChild ( darkNorthMaterial )
darkNorthSeparator . addChild ( darkNorthFaceset )
compasssep . addChild ( coords )
compasssep . addChild ( self . transform )
compasssep . addChild ( lightGreySeparator )
compasssep . addChild ( darkGreySeparator )
compasssep . addChild ( lightNorthSeparator )
compasssep . addChild ( darkNorthSeparator )
return self . compassswitch
def buildCoordinates ( self ) :
from pivy import coin
coords = coin . SoCoordinate3 ( )
# North Arrow
coords . point . set1Value ( 0 , 0 , 0 , 0 )
coords . point . set1Value ( 1 , COMPASS_POINTER_WIDTH ,
COMPASS_POINTER_WIDTH , 0 )
coords . point . set1Value ( 2 , 0 , COMPASS_POINTER_LENGTH , 0 )
coords . point . set1Value ( 3 , - COMPASS_POINTER_WIDTH ,
COMPASS_POINTER_WIDTH , 0 )
# East Arrow
coords . point . set1Value ( 4 , 0 , 0 , 0 )
coords . point . set1Value (
5 , COMPASS_POINTER_WIDTH , - COMPASS_POINTER_WIDTH , 0 )
coords . point . set1Value ( 6 , COMPASS_POINTER_LENGTH , 0 , 0 )
coords . point . set1Value ( 7 , COMPASS_POINTER_WIDTH ,
COMPASS_POINTER_WIDTH , 0 )
# South Arrow
coords . point . set1Value ( 8 , 0 , 0 , 0 )
coords . point . set1Value (
9 , - COMPASS_POINTER_WIDTH , - COMPASS_POINTER_WIDTH , 0 )
coords . point . set1Value ( 10 , 0 , - COMPASS_POINTER_LENGTH , 0 )
coords . point . set1Value (
11 , COMPASS_POINTER_WIDTH , - COMPASS_POINTER_WIDTH , 0 )
# West Arrow
coords . point . set1Value ( 12 , 0 , 0 , 0 )
coords . point . set1Value ( 13 , - COMPASS_POINTER_WIDTH ,
COMPASS_POINTER_WIDTH , 0 )
coords . point . set1Value ( 14 , - COMPASS_POINTER_LENGTH , 0 , 0 )
coords . point . set1Value (
15 , - COMPASS_POINTER_WIDTH , - COMPASS_POINTER_WIDTH , 0 )
return coords
class _PVPlantSite ( ArchSite . _Site ) :
" The Site object "
def __init__ ( self , obj ) :
ArchSite . _Site . __init__ ( self , obj )
self . obj = obj
# self.setProperties(obj)
self . Type = " Site "
obj . Proxy = self
obj . IfcType = " Site "
obj . setEditorMode ( " IfcType " , 1 )
def setProperties ( self , obj ) :
# Definicion de Propiedades:
ArchSite . _Site . setProperties ( self , obj )
obj . addProperty ( " App::PropertyLink " ,
" Boundary " ,
2025-07-06 01:12:08 +02:00
" PVPlant " ,
2025-01-28 00:04:13 +01:00
" Boundary of land " )
obj . addProperty ( " App::PropertyLinkList " ,
" Frames " ,
2025-07-06 01:12:08 +02:00
" PVPlant " ,
2025-01-28 00:04:13 +01:00
" Frames templates " )
obj . addProperty ( " App::PropertyEnumeration " ,
" UtmZone " ,
2025-07-06 01:12:08 +02:00
" PVPlant " ,
2025-01-28 00:04:13 +01:00
" UTM zone " ) . UtmZone = zone_list
obj . addProperty ( " App::PropertyVector " ,
" Origin " ,
2025-07-06 01:12:08 +02:00
" PVPlant " ,
2025-01-28 00:04:13 +01:00
" Origin point. " ) . Origin = ( 0 , 0 , 0 )
def onDocumentRestored ( self , obj ) :
""" Method run when the document is restored. Re-adds the properties. """
self . obj = obj
self . Type = " Site "
obj . Proxy = self
def onChanged ( self , obj , prop ) :
ArchSite . _Site . onChanged ( self , obj , prop )
if ( prop == " Terrain " ) or ( prop == " Boundary " ) :
if obj . Terrain and obj . Boundary :
# TODO: Definir los objetos que se pueden proyectar
# if obj.Boundary.TypeId == 'Part::Part2DObject':
# projectWireOnMesh(obj.Boundary, obj.Terrain.Mesh)
print ( " Calcular 3D boundary " )
if prop == " UtmZone " :
node = self . get_geoorigin ( )
zone = obj . getPropertyByName ( " UtmZone " )
geo_system = [ " UTM " , zone , " FLAT " ]
node . geoSystem . setValues ( geo_system )
if prop == " Origin " :
node = self . get_geoorigin ( )
origin = obj . getPropertyByName ( " Origin " )
node . geoCoords . setValue ( origin . x , origin . y , 0 )
obj . Placement . Base = obj . getPropertyByName ( prop )
def execute ( self , obj ) :
return
if not obj . isDerivedFrom ( " Part::Feature " ) : # old-style Site
return
pl = obj . Placement
shape = None
if obj . Terrain :
if obj . Terrain . isDerivedFrom ( " Part::Feature " ) :
if obj . Terrain . Shape :
if not obj . Terrain . Shape . isNull ( ) :
shape = obj . Terrain . Shape . copy ( )
if shape :
shells = [ ]
for sub in obj . Subtractions :
if sub . isDerivedFrom ( " Part::Feature " ) :
if sub . Shape :
if sub . Shape . Solids :
for sol in sub . Shape . Solids :
rest = shape . cut ( sol )
shells . append ( sol . Shells [ 0 ] . common ( shape . extrude ( obj . ExtrusionVector ) ) )
shape = rest
for sub in obj . Additions :
if sub . isDerivedFrom ( " Part::Feature " ) :
if sub . Shape :
if sub . Shape . Solids :
for sol in sub . Shape . Solids :
rest = shape . cut ( sol )
shells . append ( sol . Shells [ 0 ] . cut ( shape . extrude ( obj . ExtrusionVector ) ) )
shape = rest
if not shape . isNull ( ) :
if shape . isValid ( ) :
for shell in shells :
shape = shape . fuse ( shell )
if obj . RemoveSplitter :
shape = shape . removeSplitter ( )
obj . Shape = shape
if not pl . isNull ( ) :
obj . Placement = pl
self . computeAreas ( obj )
def computeAreas ( self , obj ) :
ArchSite . _Site . computeAreas ( self , obj )
return
if not obj . Shape :
return
if obj . Shape . isNull ( ) :
return
if not obj . Shape . isValid ( ) :
return
if not obj . Shape . Faces :
return
if not hasattr ( obj , " Perimeter " ) : # check we have a latest version site
return
if not obj . Terrain :
return
# compute area
fset = [ ]
for f in obj . Shape . Faces :
if f . normalAt ( 0 , 0 ) . getAngle ( FreeCAD . Vector ( 0 , 0 , 1 ) ) < 1.5707 :
fset . append ( f )
if fset :
import Drawing , Part
pset = [ ]
for f in fset :
try :
pf = Part . Face ( Part . Wire ( Drawing . project ( f , FreeCAD . Vector ( 0 , 0 , 1 ) ) [ 0 ] . Edges ) )
except Part . OCCError :
# error in computing the area. Better set it to zero than show a wrong value
if obj . ProjectedArea . Value != 0 :
print ( " Error computing areas for " , obj . Label )
obj . ProjectedArea = 0
else :
pset . append ( pf )
if pset :
self . flatarea = pset . pop ( )
for f in pset :
self . flatarea = self . flatarea . fuse ( f )
self . flatarea = self . flatarea . removeSplitter ( )
if obj . ProjectedArea . Value != self . flatarea . Area :
obj . ProjectedArea = self . flatarea . Area
# compute perimeter
lut = { }
for e in obj . Shape . Edges :
lut . setdefault ( e . hashCode ( ) , [ ] ) . append ( e )
l = 0
for e in lut . values ( ) :
if len ( e ) == 1 : # keep only border edges
l + = e [ 0 ] . Length
if l :
if obj . Perimeter . Value != l :
obj . Perimeter = l
# compute volumes
if obj . Terrain . Shape . Solids :
shapesolid = obj . Terrain . Shape . copy ( )
else :
shapesolid = obj . Terrain . Shape . extrude ( obj . ExtrusionVector )
addvol = 0
subvol = 0
for sub in obj . Subtractions :
subvol + = sub . Shape . common ( shapesolid ) . Volume
for sub in obj . Additions :
addvol + = sub . Shape . cut ( shapesolid ) . Volume
if obj . SubtractionVolume . Value != subvol :
obj . SubtractionVolume = subvol
if obj . AdditionVolume . Value != addvol :
obj . AdditionVolume = addvol
def __getstate__ ( self ) :
"""
Save variables to file .
"""
node = self . get_geoorigin ( )
system = node . geoSystem . getValues ( )
x , y , z = node . geoCoords . getValue ( ) . getValue ( )
return system , [ x , y , z ]
def __setstate__ ( self , state ) :
"""
Get variables from file .
"""
if state :
system = state [ 0 ]
origin = state [ 1 ]
node = self . get_geoorigin ( )
node . geoSystem . setValues ( system )
node . geoCoords . setValue ( origin [ 0 ] , origin [ 1 ] , 0 )
def get_geoorigin ( self ) :
sg = FreeCADGui . ActiveDocument . ActiveView . getSceneGraph ( )
node = sg . getChild ( 0 )
if not isinstance ( node , coin . SoGeoOrigin ) :
node = coin . SoGeoOrigin ( )
sg . insertChild ( node , 0 )
return node
def setLatLon ( self , lat , lon ) :
import utm
import PVPlantImportGrid
x , y , zone_number , zone_letter = utm . from_latlon ( lat , lon )
self . obj . UtmZone = zone_list [ zone_number - 1 ]
2025-07-06 01:12:08 +02:00
zz = PVPlantImportGrid . getElevationFromOE ( [ [ lat , lon ] ] )
self . obj . Origin = FreeCAD . Vector ( x * 1000 , y * 1000 , zz [ 0 ] . z )
#self.obj.OriginOffset = FreeCAD.Vector(x * 1000, y * 1000, 0) #??
self . obj . Latitude = lat
self . obj . Longitude = lon
self . obj . Elevation = zz [ 0 ] . z
2025-01-28 00:04:13 +01:00
class _ViewProviderSite ( ArchSite . _ViewProviderSite ) :
""" A View Provider for the Site object.
Parameters
- - - - - - - - - -
vobj : < Gui . ViewProviderDocumentObject >
The view provider to turn into a site view provider .
"""
def __init__ ( self , vobj ) :
ArchSite . _ViewProviderSite . __init__ ( self , vobj )
vobj . Proxy = self
#vobj.addExtension("Gui::ViewProviderGroupExtensionPython", self)
#self.setProperties(vobj)
def getIcon ( self ) :
"""
Return the path to the appropriate icon .
"""
return str ( os . path . join ( DirIcons , " icon.svg " ) )
def claimChildren ( self ) :
""" Define which objects will appear as children in the tree view.
Set objects within the site group , and the terrain object as children .
If the Arch preference swallowSubtractions is true , set the additions
and subtractions to the terrain as children .
Returns
- - - - - - -
list of < App : : DocumentObject > s :
The objects claimed as children .
"""
objs = [ ]
if hasattr ( self , " Object " ) :
objs = self . Object . Group + \
[ self . Object . Terrain , self . Object . Boundary , self . Object . Frames ]
if hasattr ( self . Object , " Frames " ) :
objs . extend ( self . Object . Frames )
return objs
'''
class _ViewProviderSite :
""" A View Provider for the Site object.
Parameters
- - - - - - - - - -
vobj : < Gui . ViewProviderDocumentObject >
The view provider to turn into a site view provider .
"""
def __init__ ( self , vobj ) :
vobj . Proxy = self
vobj . addExtension ( " Gui::ViewProviderGroupExtensionPython " , self )
self . setProperties ( vobj )
def setProperties ( self , vobj ) :
""" Give the site view provider its site view provider specific properties.
These include solar diagram and compass data , dealing the orientation
of the site , and its orientation to the sun .
You can learn more about properties here : https : / / wiki . freecadweb . org / property
"""
pl = vobj . PropertiesList
if not " WindRose " in pl :
vobj . addProperty ( " App::PropertyBool " , " WindRose " , " Site " , QT_TRANSLATE_NOOP ( " App::Property " , " Show wind rose diagram or not. Uses solar diagram scale. Needs Ladybug module " ) )
if not " SolarDiagram " in pl :
vobj . addProperty ( " App::PropertyBool " , " SolarDiagram " , " Site " , QT_TRANSLATE_NOOP ( " App::Property " , " Show solar diagram or not " ) )
if not " SolarDiagramScale " in pl :
vobj . addProperty ( " App::PropertyFloat " , " SolarDiagramScale " , " Site " , QT_TRANSLATE_NOOP ( " App::Property " , " The scale of the solar diagram " ) )
vobj . SolarDiagramScale = 1
if not " SolarDiagramPosition " in pl :
vobj . addProperty ( " App::PropertyVector " , " SolarDiagramPosition " , " Site " , QT_TRANSLATE_NOOP ( " App::Property " , " The position of the solar diagram " ) )
if not " SolarDiagramColor " in pl :
vobj . addProperty ( " App::PropertyColor " , " SolarDiagramColor " , " Site " , QT_TRANSLATE_NOOP ( " App::Property " , " The color of the solar diagram " ) )
vobj . SolarDiagramColor = ( 0.16 , 0.16 , 0.25 )
if not " Orientation " in pl :
vobj . addProperty ( " App::PropertyEnumeration " , " Orientation " , " Site " , QT_TRANSLATE_NOOP (
" App::Property " , " When set to ' True North ' the whole geometry will be rotated to match the true north of this site " ) )
vobj . Orientation = [ " Project North " , " True North " ]
vobj . Orientation = " Project North "
if not " Compass " in pl :
vobj . addProperty ( " App::PropertyBool " , " Compass " , " Compass " , QT_TRANSLATE_NOOP ( " App::Property " , " Show compass or not " ) )
if not " CompassRotation " in pl :
vobj . addProperty ( " App::PropertyAngle " , " CompassRotation " , " Compass " , QT_TRANSLATE_NOOP ( " App::Property " , " The rotation of the Compass relative to the Site " ) )
if not " CompassPosition " in pl :
vobj . addProperty ( " App::PropertyVector " , " CompassPosition " , " Compass " , QT_TRANSLATE_NOOP ( " App::Property " , " The position of the Compass relative to the Site placement " ) )
if not " UpdateDeclination " in pl :
vobj . addProperty ( " App::PropertyBool " , " UpdateDeclination " , " Compass " , QT_TRANSLATE_NOOP ( " App::Property " , " Update the Declination value based on the compass rotation " ) )
def onDocumentRestored ( self , vobj ) :
""" Method run when the document is restored. Re-add the Arch component properties. """
self . setProperties ( vobj )
def getIcon ( self ) :
""" Return the path to the appropriate icon. """
return str ( os . path . join ( DirIcons , " solar-panel.svg " ) )
def claimChildren ( self ) :
""" Define which objects will appear as children in the tree view.
Set objects within the site group , and the terrain object as children .
If the Arch preference swallowSubtractions is true , set the additions
and subtractions to the terrain as children .
Returns
- - - - - - -
list of < App : : DocumentObject > s :
The objects claimed as children .
"""
objs = [ ]
if hasattr ( self , " Object " ) :
objs = self . Object . Group + [ self . Object . Terrain ]
prefs = FreeCAD . ParamGet ( " User parameter:BaseApp/Preferences/Mod/Arch " )
if hasattr ( self . Object , " Additions " ) and prefs . GetBool ( " swallowAdditions " , True ) :
objs . extend ( self . Object . Additions )
if hasattr ( self . Object , " Subtractions " ) and prefs . GetBool ( " swallowSubtractions " , True ) :
objs . extend ( self . Object . Subtractions )
return objs
def setEdit ( self , vobj , mode ) :
""" Method called when the document requests the object to enter edit mode.
Edit mode is entered when a user double clicks on an object in the tree
view , or when they use the menu option [ Edit - > Toggle Edit Mode ] .
Just display the standard Arch component task panel .
Parameters
- - - - - - - - - -
mode : int or str
The edit mode the document has requested . Set to 0 when requested via
a double click or [ Edit - > Toggle Edit Mode ] .
Returns
- - - - - - -
bool
If edit mode was entered .
"""
if ( mode == 0 ) and hasattr ( self , " Object " ) :
import ArchComponent
taskd = ArchComponent . ComponentTaskPanel ( )
taskd . obj = self . Object
taskd . update ( )
FreeCADGui . Control . showDialog ( taskd )
return True
return False
def unsetEdit ( self , vobj , mode ) :
""" Method called when the document requests the object exit edit mode.
Close the Arch component edit task panel .
Returns
- - - - - - -
False
"""
FreeCADGui . Control . closeDialog ( )
return False
def attach ( self , vobj ) :
""" Add display modes ' data to the coin scenegraph.
Add each display mode as a coin node , whose parent is this view
provider .
Each display mode ' s node includes the data needed to display the object
in that mode . This might include colors of faces , or the draw style of
lines . This data is stored as additional coin nodes which are children
of the display mode node .
Doe not add display modes , but do add the solar diagram and compass to
the scenegraph .
"""
self . Object = vobj . Object
from pivy import coin
basesep = coin . SoSeparator ( )
vobj . Annotation . addChild ( basesep )
self . color = coin . SoBaseColor ( )
self . coords = coin . SoTransform ( )
basesep . addChild ( self . coords )
basesep . addChild ( self . color )
self . diagramsep = coin . SoSeparator ( )
self . diagramswitch = coin . SoSwitch ( )
self . diagramswitch . whichChild = - 1
self . diagramswitch . addChild ( self . diagramsep )
basesep . addChild ( self . diagramswitch )
self . windrosesep = coin . SoSeparator ( )
self . windroseswitch = coin . SoSwitch ( )
self . windroseswitch . whichChild = - 1
self . windroseswitch . addChild ( self . windrosesep )
basesep . addChild ( self . windroseswitch )
self . compass = Compass ( )
self . updateCompassVisibility ( vobj )
self . updateCompassScale ( vobj )
self . rotateCompass ( vobj )
vobj . Annotation . addChild ( self . compass . rootNode )
def updateData ( self , obj , prop ) :
""" Method called when the host object has a property changed.
If the Longitude or Latitude has changed , set the SolarDiagram to
update .
If Terrain or Placement has changed , move the compass to follow it .
Parameters
- - - - - - - - - -
obj : < App : : FeaturePython >
The host object that has changed .
prop : string
The name of the property that has changed .
"""
if prop in [ " Longitude " , " Latitude " ] :
self . onChanged ( obj . ViewObject , " SolarDiagram " )
elif prop == " Declination " :
self . onChanged ( obj . ViewObject , " SolarDiagramPosition " )
self . updateTrueNorthRotation ( )
elif prop == " Terrain " :
self . updateCompassLocation ( obj . ViewObject )
elif prop == " Placement " :
self . updateCompassLocation ( obj . ViewObject )
self . updateDeclination ( obj . ViewObject )
elif prop == " ProjectedArea " :
self . updateCompassScale ( obj . ViewObject )
def onChanged ( self , vobj , prop ) :
if prop == " SolarDiagramPosition " :
if hasattr ( vobj , " SolarDiagramPosition " ) :
p = vobj . SolarDiagramPosition
self . coords . translation . setValue ( [ p . x , p . y , p . z ] )
if hasattr ( vobj . Object , " Declination " ) :
from pivy import coin
self . coords . rotation . setValue ( coin . SbVec3f ( ( 0 , 0 , 1 ) ) , math . radians ( vobj . Object . Declination . Value ) )
elif prop == " SolarDiagramColor " :
if hasattr ( vobj , " SolarDiagramColor " ) :
l = vobj . SolarDiagramColor
self . color . rgb . setValue ( [ l [ 0 ] , l [ 1 ] , l [ 2 ] ] )
elif " SolarDiagram " in prop :
if hasattr ( self , " diagramnode " ) :
self . diagramsep . removeChild ( self . diagramnode )
del self . diagramnode
if hasattr ( vobj , " SolarDiagram " ) and hasattr ( vobj , " SolarDiagramScale " ) :
if vobj . SolarDiagram :
tz = 0
if hasattr ( vobj . Object , " TimeZone " ) :
tz = vobj . Object . TimeZone
self . diagramnode = makeSolarDiagram ( vobj . Object . Longitude , vobj . Object . Latitude , vobj . SolarDiagramScale , tz = tz )
if self . diagramnode :
self . diagramsep . addChild ( self . diagramnode )
self . diagramswitch . whichChild = 0
else :
del self . diagramnode
else :
self . diagramswitch . whichChild = - 1
elif prop == " WindRose " :
if hasattr ( self , " windrosenode " ) :
del self . windrosenode
if hasattr ( vobj , " WindRose " ) :
if vobj . WindRose :
if hasattr ( vobj . Object , " EPWFile " ) and vobj . Object . EPWFile :
try :
import ladybug
except :
pass
else :
self . windrosenode = makeWindRose ( vobj . Object . EPWFile , vobj . SolarDiagramScale )
if self . windrosenode :
self . windrosesep . addChild ( self . windrosenode )
self . windroseswitch . whichChild = 0
else :
del self . windrosenode
else :
self . windroseswitch . whichChild = - 1
elif prop == ' Visibility ' :
if vobj . Visibility :
self . updateCompassVisibility ( self . Object )
else :
self . compass . hide ( )
elif prop == ' Orientation ' :
if vobj . Orientation == ' True North ' :
self . addTrueNorthRotation ( )
else :
self . removeTrueNorthRotation ( )
elif prop == " UpdateDeclination " :
self . updateDeclination ( vobj )
elif prop == " Compass " :
self . updateCompassVisibility ( vobj )
elif prop == " CompassRotation " :
self . updateDeclination ( vobj )
self . rotateCompass ( vobj )
elif prop == " CompassPosition " :
self . updateCompassLocation ( vobj )
def updateDeclination ( self , vobj ) :
""" Update the declination of the compass
Update the declination by adding together how the site has been rotated
within the document , and the rotation of the site compass .
"""
if not hasattr ( vobj , ' UpdateDeclination ' ) or not vobj . UpdateDeclination :
return
compassRotation = vobj . CompassRotation . Value
siteRotation = math . degrees ( vobj . Object . Placement . Rotation . Angle ) # This assumes Rotation.axis = (0,0,1)
vobj . Object . Declination = compassRotation + siteRotation
def addTrueNorthRotation ( self ) :
if hasattr ( self , ' trueNorthRotation ' ) and self . trueNorthRotation is not None :
return
from pivy import coin
self . trueNorthRotation = coin . SoTransform ( )
sg = FreeCADGui . ActiveDocument . ActiveView . getSceneGraph ( )
sg . insertChild ( self . trueNorthRotation , 0 )
self . updateTrueNorthRotation ( )
def removeTrueNorthRotation ( self ) :
if hasattr ( self , ' trueNorthRotation ' ) and self . trueNorthRotation is not None :
sg = FreeCADGui . ActiveDocument . ActiveView . getSceneGraph ( )
sg . removeChild ( self . trueNorthRotation )
self . trueNorthRotation = None
def updateTrueNorthRotation ( self ) :
if hasattr ( self , ' trueNorthRotation ' ) and self . trueNorthRotation is not None :
from pivy import coin
angle = self . Object . Declination . Value
self . trueNorthRotation . rotation . setValue ( coin . SbVec3f ( 0 , 0 , 1 ) , math . radians ( - angle ) )
def updateCompassVisibility ( self , vobj ) :
if not hasattr ( self , ' compass ' ) :
return
show = hasattr ( vobj , ' Compass ' ) and vobj . Compass
if show :
self . compass . show ( )
else :
self . compass . hide ( )
def rotateCompass ( self , vobj ) :
if not hasattr ( self , ' compass ' ) :
return
if hasattr ( vobj , ' CompassRotation ' ) :
self . compass . rotate ( vobj . CompassRotation . Value )
def updateCompassLocation ( self , vobj ) :
if not hasattr ( self , ' compass ' ) :
return
if not vobj . Object . Shape :
return
boundBox = vobj . Object . Shape . BoundBox
pos = vobj . Object . Placement . Base
x = 0
y = 0
if hasattr ( vobj , " CompassPosition " ) :
x = vobj . CompassPosition . x
y = vobj . CompassPosition . y
z = boundBox . ZMax = pos . z
self . compass . locate ( x , y , z + 1000 )
def updateCompassScale ( self , vobj ) :
if not hasattr ( self , ' compass ' ) :
return
self . compass . scale ( vobj . Object . ProjectedArea )
def __getstate__ ( self ) :
return None
def __setstate__ ( self , state ) :
return None
'''
2025-03-28 19:40:11 +06:00
''' class _CommandPVPlantSite:
2025-01-28 00:04:13 +01:00
" the Arch Site command definition "
def GetResources ( self ) :
return { ' Pixmap ' : str ( os . path . join ( DirIcons , " icon.svg " ) ) ,
' MenuText ' : QT_TRANSLATE_NOOP ( " Arch_Site " , " Site " ) ,
' Accel ' : " S, I " ,
' ToolTip ' : QT_TRANSLATE_NOOP ( " Arch_Site " , " Creates a site object including selected objects. " ) }
def IsActive ( self ) :
return ( ( not ( FreeCAD . ActiveDocument is None ) ) and
( FreeCAD . ActiveDocument . getObject ( " Site " ) is None ) )
def Activated ( self ) :
makePVPlantSite ( )
return
if FreeCAD . GuiUp :
2025-03-28 19:40:11 +06:00
FreeCADGui . addCommand ( ' PVPlantSite ' , _CommandPVPlantSite ( ) ) '''