Merge pull request #8409 from Roy-043/Arch-fix-Arch_Site

[Arch] fix Arch_Site
This commit is contained in:
Yorik van Havre
2023-02-13 14:40:23 +01:00
committed by GitHub

View File

@@ -618,7 +618,7 @@ class _Site(ArchIFC.IfcProduct):
if not "ProjectedArea" in pl:
obj.addProperty("App::PropertyArea","ProjectedArea","Site",QT_TRANSLATE_NOOP("App::Property","The area of the projection of this object onto the XY plane"))
if not "Perimeter" in pl:
obj.addProperty("App::PropertyLength","Perimeter","Site",QT_TRANSLATE_NOOP("App::Property","The perimeter length of this terrain"))
obj.addProperty("App::PropertyLength","Perimeter","Site",QT_TRANSLATE_NOOP("App::Property","The perimeter length of the projected area"))
if not "AdditionVolume" in pl:
obj.addProperty("App::PropertyVolume","AdditionVolume","Site",QT_TRANSLATE_NOOP("App::Property","The volume of earth to be added to this terrain"))
if not "SubtractionVolume" in pl:
@@ -650,8 +650,6 @@ class _Site(ArchIFC.IfcProduct):
def execute(self,obj):
"""Method run when the object is recomputed.
If the site has no Shape or Terrain property assigned, do nothing.
Perform additions and subtractions on terrain, and assign to the site's
Shape.
"""
@@ -659,48 +657,71 @@ class _Site(ArchIFC.IfcProduct):
if not hasattr(obj,'Shape'): # old-style Site
return
pl = obj.Placement
import Part
pl = FreeCAD.Placement(obj.Placement)
shape = None
if obj.Terrain:
if hasattr(obj.Terrain,'Shape'):
if obj.Terrain.Shape:
if not obj.Terrain.Shape.isNull():
shape = obj.Terrain.Shape.copy()
if obj.Terrain is not None \
and hasattr(obj.Terrain,'Shape') \
and not obj.Terrain.Shape.isNull() \
and obj.Terrain.Shape.isValid():
shape = Part.Shape(obj.Terrain.Shape)
# Fuse and cut operations return a shape with a default placement.
# We need to transform our shape accordingly to get a consistent
# result with or without fuse or cut operations:
shape = shape.transformGeometry((shape.Placement * pl).Matrix)
shape.Placement = FreeCAD.Placement()
if shape:
shells = []
for sub in obj.Subtractions:
if hasattr(sub,'Shape'):
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 hasattr(sub,'Shape'):
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)
if shape.Solids:
for sub in obj.Additions:
if hasattr(sub,'Shape') and sub.Shape and sub.Shape.Solids:
for sol in sub.Shape.Solids:
shape = shape.fuse(sol)
for sub in obj.Subtractions:
if hasattr(sub,'Shape') and sub.Shape and sub.Shape.Solids:
for sol in sub.Shape.Solids:
shape = shape.cut(sol)
elif shape.Faces:
shells = []
for sub in obj.Additions:
if hasattr(sub,'Shape') and sub.Shape and 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
for sub in obj.Subtractions:
if hasattr(sub,'Shape') and sub.Shape and 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 shell in shells:
shape = shape.fuse(shell)
if not shape.isNull() and shape.isValid():
if obj.RemoveSplitter:
shape = shape.removeSplitter()
# Transform the shape to counteract the effect of placement pl
# and then apply that placement:
obj.Shape = shape.transformGeometry(pl.inverse().Matrix)
obj.Placement = pl
else:
shape = None
if shape is None:
obj.Shape = Part.Shape()
obj.Placement = pl
self.computeAreas(obj)
if FreeCAD.GuiUp:
vobj = obj.ViewObject
if vobj.Proxy is not None:
vobj.Proxy.updateDisplaymodeTerrainSwitches(vobj)
def onChanged(self,obj,prop):
"""Method called when the object has a property changed.
If Terrain has changed, hide the base object terrain, then run
.execute().
If Terrain has changed, hide the base object terrain.
Also call ArchIFC.IfcProduct.onChanged().
@@ -715,88 +736,76 @@ class _Site(ArchIFC.IfcProduct):
if obj.Terrain:
if FreeCAD.GuiUp:
obj.Terrain.ViewObject.hide()
self.execute(obj)
def computeAreas(self,obj):
"""Compute the area, perimeter length, and volume of the terrain shape.
Compute the area of the terrain projected onto an XY hyperplane, IE:
Compute the area of the terrain projected onto an XY plane, IE:
the area of the terrain if viewed from a birds eye view.
Compute the length of the perimeter of this birds eye view area.
Compute the volume of the terrain that needs to be subtracted and
added on account of the Additions and Subtractions to the site.
Compute the volume of the terrain that needs to be added and subtracted
on account of the Additions and Subtractions of the site.
Assign these values to their respective site properties.
"""
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:
if not obj.Shape.Faces:
if obj.ProjectedArea.Value != 0:
obj.ProjectedArea = 0
if obj.Perimeter.Value != 0:
obj.Perimeter = 0
if obj.AdditionVolume.Value != 0:
obj.AdditionVolume = 0
if obj.SubtractionVolume.Value != 0:
obj.SubtractionVolume = 0
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 TechDraw, Part
pset = []
for f in fset:
try:
pf = Part.Face(Part.Wire(TechDraw.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)
import TechDraw, Part
area = 0
perim = 0
addvol = 0
subvol = 0
for sub in obj.Subtractions:
subvol += sub.Shape.common(shapesolid).Volume
edges = []
for face in obj.Shape.Faces:
if face.normalAt(0,0).getAngle(FreeCAD.Vector(0,0,1)) < 1.5707:
edges.extend(TechDraw.project(face,FreeCAD.Vector(0,0,1))[0].Edges)
outer = TechDraw.findOuterWire(edges)
# compute area
try:
area = Part.Face(outer).Area # outer.Area does not always work.
except Part.OCCError:
print("Error computing areas for", obj.Label)
area = 0
# compute perimeter
perim = outer.Length
# compute volumes
shape = Part.Shape(obj.Terrain.Shape)
shape.Placement = obj.Placement * shape.Placement
if not obj.Terrain.Shape.Solids:
shape = shape.extrude(obj.ExtrusionVector)
for sub in obj.Additions:
addvol += sub.Shape.cut(shapesolid).Volume
if obj.SubtractionVolume.Value != subvol:
obj.SubtractionVolume = subvol
addvol += sub.Shape.cut(shape).Volume
for sub in obj.Subtractions:
subvol += sub.Shape.common(shape).Volume
# update properties
if obj.ProjectedArea.Value != area:
obj.ProjectedArea = area
if obj.Perimeter.Value != perim:
obj.Perimeter = perim
if obj.AdditionVolume.Value != addvol:
obj.AdditionVolume = addvol
if obj.SubtractionVolume.Value != subvol:
obj.SubtractionVolume = subvol
def addObject(self,obj,child):
@@ -807,6 +816,14 @@ class _Site(ArchIFC.IfcProduct):
g.append(child)
obj.Group = g
def __getstate__(self):
return None
def __setstate__(self,state):
return None
class _ViewProviderSite:
"""A View Provider for the Site object.
@@ -858,10 +875,6 @@ class _ViewProviderSite:
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.
@@ -956,18 +969,8 @@ class _ViewProviderSite:
FreeCADGui.ActiveDocument.setEdit(self.Object, 1)
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.
"""Adds the solar diagram and compass to the coin scenegraph, but does
not add display modes.
"""
self.Object = vobj.Object
@@ -1023,8 +1026,58 @@ class _ViewProviderSite:
elif prop == "ProjectedArea":
self.updateCompassScale(obj.ViewObject)
def addDisplaymodeTerrainSwitches(self,vobj):
"""Adds 'terrain' switches to the 4 default display modes.
If the Terrain property of the site is None, the 'normal' display can
be switched off with these switches. This avoids 'ghosts' of the objects
in the Group property.
See:
https://forum.freecad.org/viewtopic.php?f=10&t=74731
https://forum.freecad.org/viewtopic.php?t=75658
https://forum.freecad.org/viewtopic.php?t=75883
"""
if not hasattr(self, "terrain_switches"):
main_switch = vobj.RootNode.getChild(2) # The display mode switch.
if main_switch.getNumChildren() == 4: # Check if all display modes are available.
from pivy import coin
self.terrain_switches = []
for node in tuple(main_switch.getChildren()):
new_switch = coin.SoSwitch()
sep1 = coin.SoSeparator()
sep1.setName("NoTerrain")
sep2 = coin.SoSeparator()
sep2.setName("Terrain")
child_list = list(node.getChildren())
for child in child_list:
sep2.addChild(child)
new_switch.addChild(sep1)
new_switch.addChild(sep2)
new_switch.whichChild = 0
node.addChild(new_switch)
for i in range(len(child_list)):
node.removeChild(0) # Remove the original children.
self.terrain_switches.append(new_switch)
def updateDisplaymodeTerrainSwitches(self,vobj):
"""Updates the 'terrain' switches."""
if not hasattr(self, "terrain_switches"):
return
idx = 0 if self.Object.Terrain is None else 1
for switch in self.terrain_switches:
switch.whichChild = idx
def onChanged(self,vobj,prop):
# onChanged is called multiple times when a document is opened.
# Some display mode nodes can be missing during initial calls.
# The two methods called below must take that into account.
self.addDisplaymodeTerrainSwitches(vobj)
self.updateDisplaymodeTerrainSwitches(vobj)
if prop == "SolarDiagramPosition":
if hasattr(vobj,"SolarDiagramPosition"):
p = vobj.SolarDiagramPosition