Arch: massive rewrite of extrusion-based Arch objects and IFC export

This commit is contained in:
Yorik van Havre 2016-10-27 15:08:16 -02:00
parent bd8a296be6
commit 65aaf16201
8 changed files with 666 additions and 622 deletions

View File

@ -432,14 +432,25 @@ def getShapeFromMesh(mesh,fast=True,tolerance=0.001,flat=False,cut=True):
try: try:
f = Part.Face(Part.makePolygon(pts)) f = Part.Face(Part.makePolygon(pts))
except: except:
pass print "getShapeFromMesh: error building face from polygon"
#pass
else: else:
faces.append(f) faces.append(f)
shell = Part.makeShell(faces) shell = Part.makeShell(faces)
solid = Part.Solid(shell) try:
solid = solid.removeSplitter() solid = Part.Solid(shell)
return solid except Part.OCCError:
print "getShapeFromMesh: error creating solid"
else:
try:
solid = solid.removeSplitter()
except Part.OCCError:
print "getShapeFromMesh: error removing splitter"
#pass
return solid
#if not mesh.isSolid():
# print "getShapeFromMesh: non-solid mesh, using slow method"
faces = [] faces = []
segments = mesh.getPlanarSegments(tolerance) segments = mesh.getPlanarSegments(tolerance)
#print len(segments) #print len(segments)
@ -462,9 +473,11 @@ def getShapeFromMesh(mesh,fast=True,tolerance=0.001,flat=False,cut=True):
if flat: if flat:
return se return se
except Part.OCCError: except Part.OCCError:
print "getShapeFromMesh: error removing splitter"
try: try:
cp = Part.makeCompound(faces) cp = Part.makeCompound(faces)
except Part.OCCError: except Part.OCCError:
print "getShapeFromMesh: error creating compound"
return None return None
else: else:
return cp return cp
@ -472,6 +485,7 @@ def getShapeFromMesh(mesh,fast=True,tolerance=0.001,flat=False,cut=True):
try: try:
solid = Part.Solid(se) solid = Part.Solid(se)
except Part.OCCError: except Part.OCCError:
print "getShapeFromMesh: error creating solid"
return se return se
else: else:
return solid return solid
@ -673,7 +687,7 @@ def pruneIncluded(objectslist,strict=False):
if obj.isDerivedFrom("Part::Feature"): if obj.isDerivedFrom("Part::Feature"):
if not (Draft.getType(obj) in ["Window","Clone","Pipe"]): if not (Draft.getType(obj) in ["Window","Clone","Pipe"]):
for parent in obj.InList: for parent in obj.InList:
if parent.isDerivedFrom("Part::Feature"): if parent.isDerivedFrom("Part::Feature") and not (Draft.getType(parent) in ["Facebinder"]):
if not parent.isDerivedFrom("Part::Part2DObject"): if not parent.isDerivedFrom("Part::Part2DObject"):
# don't consider 2D objects based on arch elements # don't consider 2D objects based on arch elements
if hasattr(parent,"CloneOf"): if hasattr(parent,"CloneOf"):
@ -936,8 +950,12 @@ def getExtrusionData(shape):
return None return None
# build faces list with normals # build faces list with normals
faces = [] faces = []
import Part
for f in shape.Faces: for f in shape.Faces:
faces.append([f,f.normalAt(0,0)]) try:
faces.append([f,f.normalAt(0,0)])
except Part.OCCError:
return None
# find opposite normals pairs # find opposite normals pairs
pairs = [] pairs = []
for i1, f1 in enumerate(faces): for i1, f1 in enumerate(faces):

View File

@ -31,7 +31,7 @@ Roles = ['Undefined','Beam','Chimney','Column','Covering','Curtain Wall',
'Member','Plate','Railing','Ramp','Ramp Flight','Rebar','Pile','Roof','Shading Device','Slab','Space', 'Member','Plate','Railing','Ramp','Ramp Flight','Rebar','Pile','Roof','Shading Device','Slab','Space',
'Stair','Stair Flight','Tendon','Wall','Wall Layer','Window'] 'Stair','Stair Flight','Tendon','Wall','Wall Layer','Window']
import FreeCAD,Draft,ArchCommands import FreeCAD,Draft,ArchCommands,math
from FreeCAD import Vector from FreeCAD import Vector
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
import FreeCADGui import FreeCADGui
@ -363,195 +363,43 @@ class Component:
siblings.append(o) siblings.append(o)
return siblings return siblings
def getAxis(self,obj): def getExtrusionData(self,obj):
"Returns an open wire which is the axis of this component, if applicable" "returns (shape,extrusion vector,placement) or None"
if Draft.getType(obj) == "Precast": if hasattr(obj,"CloneOf"):
return None if obj.CloneOf:
if obj.Base: data = obj.CloneOf.Proxy.getExtrusionData(obj.CloneOf)
if obj.Base.isDerivedFrom("Part::Feature"): if data:
if obj.Base.Shape: return data
if (len(obj.Base.Shape.Wires) == 1) and not(obj.Base.Shape.Faces):
if not obj.Base.Shape.Wires[0].isClosed():
return obj.Base.Shape.copy()
elif not(obj.Base.Shape.Solids):
if hasattr(obj.Base.Shape,"CenterOfMass"):
p1 = obj.Base.Shape.CenterOfMass
v = self.getExtrusionVector(obj)
if v:
p2 = p1.add(v)
import Part
return Part.Line(p1,p2).toShape()
else:
p1 = FreeCAD.Vector()
v = self.getExtrusionVector(obj)
if v:
p2 = p1.add(v)
import Part
return Part.Line(p1,p2).toShape()
return None
def getProfiles(self,obj,noplacement=False):
"Returns the base profile(s) of this component, if applicable"
wires = []
if Draft.getType(obj) == "Precast":
return wires
n,l,w,h = self.getDefaultValues(obj)
if obj.Base: if obj.Base:
if obj.Base.isDerivedFrom("Part::Extrusion"): if obj.Base.isDerivedFrom("Part::Extrusion"):
if obj.Base.Base: if obj.Base.Base:
base = obj.Base.Base.Shape.copy() base,placement = self.rebase(obj.Base.Base.Shape)
#if noplacement: extrusion = obj.Base.Dir
# base.Placement = FreeCAD.Placement() if extrusion.Length == 0:
return [base] extrusion = FreeCAD.Vector(0,0,1)
elif obj.Base.isDerivedFrom("Part::Feature"): if hasattr(obj.Base,"LengthForward"):
if obj.Base.Shape: if obj.Base.LengthForward.Value:
base = obj.Base.Shape.copy() extrusion = extrusion.multiply(obj.Base.LengthForward.Value)
if noplacement: return (base,extrusion,placement)
base.Placement = FreeCAD.Placement() return None
if not base.Solids:
if base.Faces:
import DraftGeomUtils
if not DraftGeomUtils.isCoplanar(base.Faces):
return []
return [base]
basewires = [] def rebase(self,shape):
if not base.Wires: import DraftGeomUtils,math
if len(base.Edges) == 1: if hasattr(shape,"CenterOfMass"):
import Part v = shape.CenterOfMass
basewires = [Part.Wire(base.Edges)]
else:
basewires = base.Wires
if basewires:
import DraftGeomUtils,DraftVecUtils,Part
for wire in basewires:
e = wire.Edges[0]
if isinstance(e.Curve,Part.Circle):
dvec = e.Vertexes[0].Point.sub(e.Curve.Center)
else:
dvec = DraftGeomUtils.vec(wire.Edges[0]).cross(n)
if not DraftVecUtils.isNull(dvec):
dvec.normalize()
sh = None
if hasattr(obj,"Align"):
if obj.Align == "Left":
dvec.multiply(w)
if hasattr(obj,"Offset"):
if obj.Offset.Value:
dvec2 = DraftVecUtils.scaleTo(dvec,obj.Offset.Value)
wire = DraftGeomUtils.offsetWire(wire,dvec2)
w2 = DraftGeomUtils.offsetWire(wire,dvec)
w1 = Part.Wire(Part.__sortEdges__(wire.Edges))
sh = DraftGeomUtils.bind(w1,w2)
elif obj.Align == "Right":
dvec.multiply(w)
dvec = dvec.negative()
if hasattr(obj,"Offset"):
if obj.Offset.Value:
dvec2 = DraftVecUtils.scaleTo(dvec,obj.Offset.Value)
wire = DraftGeomUtils.offsetWire(wire,dvec2)
w2 = DraftGeomUtils.offsetWire(wire,dvec)
w1 = Part.Wire(Part.__sortEdges__(wire.Edges))
sh = DraftGeomUtils.bind(w1,w2)
elif obj.Align == "Center":
dvec.multiply(w/2)
w1 = DraftGeomUtils.offsetWire(wire,dvec)
dvec = dvec.negative()
w2 = DraftGeomUtils.offsetWire(wire,dvec)
sh = DraftGeomUtils.bind(w1,w2)
if sh:
wires.append(sh)
else:
wires.append(wire)
elif Draft.getType(obj) in ["Wall","Structure"]:
if (Draft.getType(obj) == "Structure") and (l > h) and (obj.Role != "Slab"):
if noplacement:
h2 = h/2 or 0.5
w2 = w/2 or 0.5
v1 = Vector(-h2,-w2,0)
v2 = Vector(h2,-w2,0)
v3 = Vector(h2,w2,0)
v4 = Vector(-h2,w2,0)
else:
h2 = h/2 or 0.5
w2 = w/2 or 0.5
v1 = Vector(0,-w2,-h2)
v2 = Vector(0,-w2,h2)
v3 = Vector(0,w2,h2)
v4 = Vector(0,w2,-h2)
else:
l2 = l/2 or 0.5
w2 = w/2 or 0.5
v1 = Vector(-l2,-w2,0)
v2 = Vector(l2,-w2,0)
v3 = Vector(l2,w2,0)
v4 = Vector(-l2,w2,0)
import Part
base = Part.makePolygon([v1,v2,v3,v4,v1])
return [base]
return wires
def getExtrusionVector(self,obj,noplacement=False):
"Returns an extrusion vector of this component, if applicable"
n,l,w,h = self.getDefaultValues(obj)
if Draft.getType(obj) == "Precast":
return FreeCAD.Vector()
if obj.Base:
if obj.Base.isDerivedFrom("Part::Extrusion"):
return FreeCAD.Vector(obj.Base.Dir)
if Draft.getType(obj) == "Structure":
if l > h:
v = n.multiply(l)
if noplacement:
import DraftVecUtils
v = DraftVecUtils.rounded(FreeCAD.Rotation(FreeCAD.Vector(0,1,0),-90).multVec(v))
return v
return n.multiply(h)
def getDefaultValues(self,obj):
"returns normal,length,width,height values from this component"
length = 0
if hasattr(obj,"Length"):
if obj.Length.Value:
length = obj.Length.Value
width = 0
if hasattr(obj,"Width"):
if obj.Width.Value:
width = obj.Width.Value
height = 0
if hasattr(obj,"Height"):
if obj.Height.Value:
height = obj.Height.Value
else:
for p in obj.InList:
if Draft.getType(p) == "Floor":
if p.Height.Value:
height = p.Height.Value
default = Vector(0,0,1)
if Draft.getType(obj) == "Structure":
if length > height:
default = Vector(1,0,0)
if hasattr(obj,"Normal"):
if obj.Normal == Vector(0,0,0):
normal = default
else:
normal = Vector(obj.Normal)
else: else:
normal = default v = shape.BoundBox.Center
return normal,length,width,height n = DraftGeomUtils.getNormal(shape)
r = FreeCAD.Rotation(FreeCAD.Vector(0,0,1),n)
def getPlacement(self,obj): if round(r.Angle,8) == round(math.pi,8):
"returns a total placement for the profile of this component" r = FreeCAD.Rotation()
shape = shape.copy()
shape.translate(v.negative())
shape.rotate(FreeCAD.Vector(0,0,0),r.inverted().Axis,math.degrees(r.inverted().Angle))
p = FreeCAD.Placement() p = FreeCAD.Placement()
if obj.Base: p.Base = v
p = obj.Base.Placement.multiply(p) p.Rotation = r
else: return (shape,p)
if Draft.getType(obj) == "Structure":
n,l,w,h = self.getDefaultValues(obj)
if l > h:
p.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0,1,0),90)
p = obj.Placement.multiply(p)
return p
def hideSubobjects(self,obj,prop): def hideSubobjects(self,obj,prop):
"Hides subobjects when a subobject lists change" "Hides subobjects when a subobject lists change"
@ -701,33 +549,42 @@ class Component:
return return
if not obj.Shape.Faces: if not obj.Shape.Faces:
return return
import Drawing,Part
a = 0 a = 0
fset = [] fset = []
for f in obj.Shape.Faces: for i,f in enumerate(obj.Shape.Faces):
ang = f.normalAt(0,0).getAngle(FreeCAD.Vector(0,0,1)) try:
if (ang > 1.57) and (ang < 1.571): ang = f.normalAt(0,0).getAngle(FreeCAD.Vector(0,0,1))
a += f.Area except Part.OCCError:
if ang < 1.5707: print "Debug: Error computing areas for ",obj.Label,": normalAt() Face ",i
fset.append(f) return
else:
if (ang > 1.57) and (ang < 1.571):
a += f.Area
if ang < 1.5707:
fset.append(f)
if a and hasattr(obj,"VerticalArea"): if a and hasattr(obj,"VerticalArea"):
if obj.VerticalArea.Value != a: if obj.VerticalArea.Value != a:
obj.VerticalArea = a obj.VerticalArea = a
if fset and hasattr(obj,"HorizontalArea"): if fset and hasattr(obj,"HorizontalArea"):
import Drawing,Part
pset = [] pset = []
for f in fset: for f in fset:
try: if f.normalAt(0,0).getAngle(FreeCAD.Vector(0,0,1)) < 0.00001:
pf = Part.Face(Part.Wire(Drawing.project(f,FreeCAD.Vector(0,0,1))[0].Edges)) # already horizontal
except Part.OCCError: pset.append(f)
# error in computing the areas. Better set them to zero than show a wrong value
if obj.HorizontalArea.Value != 0:
print "Error computing areas for ",obj.Label
obj.HorizontalArea = 0
if hasattr(obj,"PerimeterLength"):
if obj.PerimeterLength.Value != 0:
obj.PerimeterLength = 0
else: else:
pset.append(pf) try:
pf = Part.Face(Part.Wire(Drawing.project(f,FreeCAD.Vector(0,0,1))[0].Edges))
except Part.OCCError:
# error in computing the areas. Better set them to zero than show a wrong value
if obj.HorizontalArea.Value != 0:
print "Debug: Error computing areas for ",obj.Label,": unable to project face: ",str([v.Point for v in f.Vertexes])," (face normal:",f.normalAt(0,0),")"
obj.HorizontalArea = 0
if hasattr(obj,"PerimeterLength"):
if obj.PerimeterLength.Value != 0:
obj.PerimeterLength = 0
else:
pset.append(pf)
if pset: if pset:
self.flatarea = pset.pop() self.flatarea = pset.pop()
for f in pset: for f in pset:

View File

@ -162,6 +162,7 @@ class _ArchPipe(ArchComponent.Component):
ArchComponent.Component.__init__(self,obj) ArchComponent.Component.__init__(self,obj)
self.Type = "Pipe" self.Type = "Pipe"
obj.Role = ["Pipe Segment"]
obj.addProperty("App::PropertyLength", "Diameter", "Arch", QT_TRANSLATE_NOOP("App::Property","The diameter of this pipe, if not based on a profile")) obj.addProperty("App::PropertyLength", "Diameter", "Arch", QT_TRANSLATE_NOOP("App::Property","The diameter of this pipe, if not based on a profile"))
obj.addProperty("App::PropertyLength", "Length", "Arch", QT_TRANSLATE_NOOP("App::Property","The length of this pipe, if not based on an edge")) obj.addProperty("App::PropertyLength", "Length", "Arch", QT_TRANSLATE_NOOP("App::Property","The length of this pipe, if not based on an edge"))
obj.addProperty("App::PropertyLink", "Profile", "Arch", QT_TRANSLATE_NOOP("App::Property","An optional closed profile to base this pipe on")) obj.addProperty("App::PropertyLink", "Profile", "Arch", QT_TRANSLATE_NOOP("App::Property","An optional closed profile to base this pipe on"))
@ -275,6 +276,7 @@ class _ArchPipeConnector(ArchComponent.Component):
ArchComponent.Component.__init__(self,obj) ArchComponent.Component.__init__(self,obj)
self.Type = "PipeConnector" self.Type = "PipeConnector"
obj.Role = ["Pipe Fitting"]
obj.addProperty("App::PropertyLength", "Radius", "Arch", QT_TRANSLATE_NOOP("App::Property","The curvature radius of this connector")) obj.addProperty("App::PropertyLength", "Radius", "Arch", QT_TRANSLATE_NOOP("App::Property","The curvature radius of this connector"))
obj.addProperty("App::PropertyLinkList", "Pipes", "Arch", QT_TRANSLATE_NOOP("App::Property","The pipes linked by this connector")) obj.addProperty("App::PropertyLinkList", "Pipes", "Arch", QT_TRANSLATE_NOOP("App::Property","The pipes linked by this connector"))
obj.addProperty("App::PropertyEnumeration", "ConnectorType", "Arch", QT_TRANSLATE_NOOP("App::Property","The type of this connector")) obj.addProperty("App::PropertyEnumeration", "ConnectorType", "Arch", QT_TRANSLATE_NOOP("App::Property","The type of this connector"))

View File

@ -56,12 +56,6 @@ class _Precast(ArchComponent.Component):
self.Type = "Precast" self.Type = "Precast"
obj.Role = ["Beam","Column","Panel","Slab","Stairs"] obj.Role = ["Beam","Column","Panel","Slab","Stairs"]
def getProfile(self,obj,noplacement=True):
return []
def getExtrusionVector(self,obj,noplacement=True):
return FreeCAD.Vector()
def execute(self,obj): def execute(self,obj):
if self.clone(obj): if self.clone(obj):

View File

@ -307,7 +307,7 @@ class _Space(ArchComponent.Component):
if obj.Base: if obj.Base:
if obj.Base.isDerivedFrom("Part::Feature"): if obj.Base.isDerivedFrom("Part::Feature"):
if obj.Base.Shape.Solids: if obj.Base.Shape.Solids:
shape = obj.Base.Shape.Solids[0].copy() shape = obj.Base.Shape.copy()
shape = shape.removeSplitter() shape = shape.removeSplitter()
# 2: if not, add all bounding boxes of considered objects and build a first shape # 2: if not, add all bounding boxes of considered objects and build a first shape

View File

@ -374,7 +374,9 @@ class _CommandStructure:
class _Structure(ArchComponent.Component): class _Structure(ArchComponent.Component):
"The Structure object" "The Structure object"
def __init__(self,obj): def __init__(self,obj):
ArchComponent.Component.__init__(self,obj) ArchComponent.Component.__init__(self,obj)
obj.addProperty("App::PropertyLink","Tool","Arch",QT_TRANSLATE_NOOP("App::Property","An optional extrusion path for this element")) obj.addProperty("App::PropertyLink","Tool","Arch",QT_TRANSLATE_NOOP("App::Property","An optional extrusion path for this element"))
@ -386,7 +388,9 @@ class _Structure(ArchComponent.Component):
obj.addProperty("App::PropertyVectorList","Nodes","Arch",QT_TRANSLATE_NOOP("App::Property","The structural nodes of this element")) obj.addProperty("App::PropertyVectorList","Nodes","Arch",QT_TRANSLATE_NOOP("App::Property","The structural nodes of this element"))
obj.addProperty("App::PropertyString","Profile","Arch",QT_TRANSLATE_NOOP("App::Property","A description of the standard profile this element is based upon")) obj.addProperty("App::PropertyString","Profile","Arch",QT_TRANSLATE_NOOP("App::Property","A description of the standard profile this element is based upon"))
obj.addProperty("App::PropertyDistance","NodesOffset","Arch",QT_TRANSLATE_NOOP("App::Property","Offset distance between the centerline and the nodes line")) obj.addProperty("App::PropertyDistance","NodesOffset","Arch",QT_TRANSLATE_NOOP("App::Property","Offset distance between the centerline and the nodes line"))
obj.addProperty("App::PropertyEnumeration","FaceMaker","Arch",QT_TRANSLATE_NOOP("App::Property","The facemaker type to use to build the profile of this object"))
self.Type = "Structure" self.Type = "Structure"
obj.FaceMaker = ["None","Simple","Cheese","Bullseye"]
obj.Role = Roles obj.Role = Roles
def execute(self,obj): def execute(self,obj):
@ -397,11 +401,22 @@ class _Structure(ArchComponent.Component):
if self.clone(obj): if self.clone(obj):
return return
normal,length,width,height = self.getDefaultValues(obj) import Part, DraftGeomUtils
# creating base shape
pl = obj.Placement
base = None base = None
pl = obj.Placement
extdata = self.getExtrusionData(obj)
if extdata:
base = extdata[0]
base.Placement = extdata[2].multiply(base.Placement)
extv = extdata[2].Rotation.multVec(extdata[1])
if obj.Tool:
try:
base = obj.Tool.Shape.copy().makePipe(obj.Base.Shape.copy())
except Part.OCCError:
FreeCAD.Console.PrintError(translate("Arch","Error: The base shape couldn't be extruded along this tool object")+"\n")
return
else:
base = base.extrude(extv)
if obj.Base: if obj.Base:
if obj.Base.isDerivedFrom("Part::Feature"): if obj.Base.isDerivedFrom("Part::Feature"):
if obj.Base.Shape.isNull(): if obj.Base.Shape.isNull():
@ -410,42 +425,8 @@ class _Structure(ArchComponent.Component):
if not obj.Base.Shape.Solids: if not obj.Base.Shape.Solids:
# let pass invalid objects if they have solids... # let pass invalid objects if they have solids...
return return
if hasattr(obj,"Tool"): elif obj.Base.Shape.Solids:
if obj.Tool:
try:
base = obj.Tool.Shape.copy().makePipe(obj.Base.Shape.copy())
except Part.OCCError:
FreeCAD.Console.PrintError(translate("Arch","Error: The base shape couldn't be extruded along this tool object"))
return
if not base:
if not height:
return
if obj.Normal == Vector(0,0,0):
if len(obj.Base.Shape.Faces) > 0 :
normal=obj.Base.Shape.Faces[0].normalAt(.5,.5)
else:
normal = DraftGeomUtils.getNormal(obj.Base.Shape)
if not normal:
normal = FreeCAD.Vector(0,0,1)
#p = FreeCAD.Placement(obj.Base.Placement)
#normal = p.Rotation.multVec(normal)
else:
normal = Vector(obj.Normal)
normal = normal.multiply(height)
base = obj.Base.Shape.copy() base = obj.Base.Shape.copy()
if base.Solids:
pass
elif base.Faces:
base = base.extrude(normal)
elif (len(base.Wires) == 1):
if base.Wires[0].isClosed():
try:
base = Part.Face(base.Wires[0])
base = base.extrude(normal)
except Part.OCCError:
FreeCAD.Console.PrintError(obj.Label+" : "+str(translate("Arch","Unable to extrude the base shape\n")))
return
elif obj.Base.isDerivedFrom("Mesh::Feature"): elif obj.Base.isDerivedFrom("Mesh::Feature"):
if obj.Base.Mesh.isSolid(): if obj.Base.Mesh.isSolid():
if obj.Base.Mesh.countComponents() == 1: if obj.Base.Mesh.countComponents() == 1:
@ -453,54 +434,133 @@ class _Structure(ArchComponent.Component):
if sh.isClosed() and sh.isValid() and sh.Solids and (not sh.isNull()): if sh.isClosed() and sh.isValid() and sh.Solids and (not sh.isNull()):
base = sh base = sh
else: else:
FreeCAD.Console.PrintWarning(str(translate("Arch","This mesh is an invalid solid"))) FreeCAD.Console.PrintWarning(translate("Arch","This mesh is an invalid solid")+"\n")
obj.Base.ViewObject.show() obj.Base.ViewObject.show()
else: if not base:
base = self.getProfiles(obj) FreeCAD.Console.PrintError(translate("Arch","Error: Invalid base object")+"\n")
if base: return
if length > height:
normal = normal.multiply(length)
else:
normal = normal.multiply(height)
base = Part.Face(base[0])
base = base.extrude(normal)
base = self.processSubShapes(obj,base,pl) base = self.processSubShapes(obj,base,pl)
self.applyShape(obj,base,pl) self.applyShape(obj,base,pl)
def getExtrusionData(self,obj):
"""returns (shape,extrusion vector,placement) or None"""
import Part,DraftGeomUtils
data = ArchComponent.Component.getExtrusionData(self,obj)
if data:
return data
length = obj.Length.Value
width = obj.Width.Value
height = obj.Height.Value
normal = None
if not height:
for p in obj.InList:
if Draft.getType(p) == "Floor":
if p.Height.Value:
height = p.Height.Value
base = None
placement = None
if obj.Base:
if obj.Base.isDerivedFrom("Part::Feature"):
if obj.Base.Shape:
if obj.Base.Shape.Solids:
return None
elif obj.Base.Shape.Faces:
if not DraftGeomUtils.isCoplanar(obj.Base.Shape.Faces):
return None
else:
base,placement = self.rebase(obj.Base.Shape)
normal = obj.Base.Shape.Faces[0].normalAt(0,0)
elif obj.Base.Shape.Wires:
baseface = None
if hasattr(obj,"FaceMaker"):
if obj.FaceMaker != "None":
try:
baseface = Part.makeFace(obj.Base.Shape.Wires,"Part::FaceMaker"+str(obj.FaceMaker))
except:
FreeCAD.Console.PrintError(translate("Arch","Facemaker returned an error")+"\n")
return None
normal = baseface.normalAt(0,0)
if not baseface:
for w in obj.Base.Shape.Wires:
w.fix(0.1,0,1) # fixes self-intersecting wires
f = Part.Face(sh)
if baseface:
baseface = baseface.fuse(f)
else:
baseface = f
normal = f.normalAt(0,0)
base,placement = self.rebase(baseface)
elif (len(obj.Base.Shape.Edges) == 1) and (len(obj.Base.Shape.Vertexes) == 1):
# closed edge
w = Part.Wire(obj.Base.Shape.Edges[0])
baseface = Part.Face(w)
base,placement = self.rebase(baseface)
elif length and width and height:
if (length > height) and (obj.Role != "Slab"):
h2 = height/2 or 0.5
w2 = width/2 or 0.5
v1 = Vector(0,-w2,-h2)
v2 = Vector(0,-w2,h2)
v3 = Vector(0,w2,h2)
v4 = Vector(0,w2,-h2)
else:
l2 = length/2 or 0.5
w2 = width/2 or 0.5
v1 = Vector(-l2,-w2,0)
v2 = Vector(l2,-w2,0)
v3 = Vector(l2,w2,0)
v4 = Vector(-l2,w2,0)
import Part
baseface = Part.Face(Part.makePolygon([v1,v2,v3,v4,v1]))
base,placement = self.rebase(baseface)
if base and placement:
if obj.Normal == Vector(0,0,0):
if not normal:
normal = Vector(0,0,1)
else:
normal = Vector(obj.Normal)
if (length > height) and (obj.Role != "Slab"):
extrusion = normal.multiply(length)
else:
extrusion = normal.multiply(height)
return (base,extrusion,placement)
return None
def onChanged(self,obj,prop): def onChanged(self,obj,prop):
self.hideSubobjects(obj,prop) self.hideSubobjects(obj,prop)
if prop in ["Shape","ResetNodes","NodesOffset"]: if prop in ["Shape","ResetNodes","NodesOffset"]:
# ResetNodes is not a property but it allows us to use this function to force reset the nodes # ResetNodes is not a property but it allows us to use this function to force reset the nodes
if hasattr(obj,"Nodes"): nodes = None
# update structural nodes extdata = self.getExtrusionData(obj)
offset = FreeCAD.Vector() if extdata:
if hasattr(obj,"NodesOffset"): nodes = extdata[0]
offset = FreeCAD.Vector(0,0,obj.NodesOffset.Value) nodes.Placement = nodes.Placement.multiply(extdata[2])
if obj.Nodes and (prop != "ResetNodes"): if obj.Role not in ["Slab"]:
if hasattr(self,"nodes"): if obj.Tool:
if self.nodes: nodes = obj.Tool.Shape
if obj.Nodes != self.nodes:
# nodes are set manually: don't touch them
return
else: else:
# nodes haven't been calculated yet, but are set (file load) import Part
# we calculate the nodes now but don't change the property nodes = Part.Line(nodes.CenterOfMass,nodes.CenterOfMass.add(extdata[1])).toShape()
if obj.Role in ["Slab"]: offset = FreeCAD.Vector()
nodes = self.getProfiles(obj)[0] if hasattr(obj,"NodesOffset"):
else: offset = FreeCAD.Vector(0,0,obj.NodesOffset.Value)
nodes = self.getAxis(obj) if obj.Nodes and (prop != "ResetNodes"):
if nodes: if hasattr(self,"nodes"):
self.nodes = [v.Point.add(offset) for v in nodes.Vertexes] if self.nodes:
if obj.Nodes != self.nodes:
# nodes are set manually: don't touch them
return return
# we calculate and set the nodes
if obj.Role in ["Slab"]:
nodes = self.getProfiles(obj)[0]
else: else:
nodes = self.getAxis(obj) # nodes haven't been calculated yet, but are set (file load)
if nodes: # we set the nodes now but don't change the property
self.nodes = [v.Point.add(offset) for v in nodes.Vertexes] if nodes:
obj.Nodes = self.nodes self.nodes = [v.Point.add(offset) for v in nodes.Vertexes]
return
# we set the nodes
if nodes:
self.nodes = [v.Point.add(offset) for v in nodes.Vertexes]
obj.Nodes = self.nodes
def getNodeEdges(self,obj): def getNodeEdges(self,obj):
"returns a list of edges from stuctural nodes" "returns a list of edges from stuctural nodes"

View File

@ -429,13 +429,15 @@ class _Wall(ArchComponent.Component):
return return
import Part, DraftGeomUtils import Part, DraftGeomUtils
pl = obj.Placement
normal,length,width,height = self.getDefaultValues(obj)
base = None base = None
face = None pl = obj.Placement
extdata = self.getExtrusionData(obj)
if extdata:
base = extdata[0]
base.Placement = extdata[2].multiply(base.Placement)
extv = extdata[2].Rotation.multVec(extdata[1])
base = base.extrude(extv)
if obj.Base: if obj.Base:
# computing a shape from a base object
if obj.Base.isDerivedFrom("Part::Feature"): if obj.Base.isDerivedFrom("Part::Feature"):
if obj.Base.Shape.isNull(): if obj.Base.Shape.isNull():
return return
@ -443,51 +445,8 @@ class _Wall(ArchComponent.Component):
if not obj.Base.Shape.Solids: if not obj.Base.Shape.Solids:
# let pass invalid objects if they have solids... # let pass invalid objects if they have solids...
return return
if hasattr(obj,"Face"):
if obj.Face > 0:
if len(obj.Base.Shape.Faces) >= obj.Face:
face = obj.Base.Shape.Faces[obj.Face-1]
if face:
# case 1: this wall is based on a specific face of its base object
normal = face.normalAt(0,0)
if normal.getAngle(Vector(0,0,1)) > math.pi/4:
normal.multiply(width)
base = face.extrude(normal)
if obj.Align == "Center":
base.translate(normal.negative().multiply(0.5))
elif obj.Align == "Right":
base.translate(normal.negative())
else:
normal.multiply(height)
base = face.extrude(normal)
elif obj.Base.Shape.Solids: elif obj.Base.Shape.Solids:
# case 2: the base is already a solid
base = obj.Base.Shape.copy() base = obj.Base.Shape.copy()
elif obj.Base.Shape.Edges:
# case 3: the base is flat, we need to extrude it
if not obj.Base.Shape.Faces:
# set the length property
if hasattr(obj.Base.Shape,"Length"):
l = obj.Base.Shape.Length
if obj.Length != l:
obj.Length = l
profiles = self.getProfiles(obj)
if profiles:
normal.multiply(height)
base = profiles.pop()
base.fix(0.1,0,1)
base = base.extrude(normal)
for p in profiles:
p.fix(0.1,0,1)
p = p.extrude(normal)
base = base.fuse(p)
else:
base = None
else:
base = None
FreeCAD.Console.PrintError(str(translate("Arch","Error: Invalid base object")))
elif obj.Base.isDerivedFrom("Mesh::Feature"): elif obj.Base.isDerivedFrom("Mesh::Feature"):
if obj.Base.Mesh.isSolid(): if obj.Base.Mesh.isSolid():
if obj.Base.Mesh.countComponents() == 1: if obj.Base.Mesh.countComponents() == 1:
@ -495,16 +454,25 @@ class _Wall(ArchComponent.Component):
if sh.isClosed() and sh.isValid() and sh.Solids and (not sh.isNull()): if sh.isClosed() and sh.isValid() and sh.Solids and (not sh.isNull()):
base = sh base = sh
else: else:
FreeCAD.Console.PrintWarning(str(translate("Arch","This mesh is an invalid solid"))) FreeCAD.Console.PrintWarning(translate("Arch","This mesh is an invalid solid")+"\n")
obj.Base.ViewObject.show() obj.Base.ViewObject.show()
else: if not base:
# computing a shape from scratch FreeCAD.Console.PrintError(translate("Arch","Error: Invalid base object")+"\n")
if length and width and height: return
base = Part.makeBox(length,width,height)
base = self.processSubShapes(obj,base,pl) base = self.processSubShapes(obj,base,pl)
self.applyShape(obj,base,pl) self.applyShape(obj,base,pl)
# set the length property
if obj.Base:
if obj.Base.isDerivedFrom("Part::Feature"):
if obj.Base.Shape.Edges:
if not obj.Base.Shape.Faces:
if hasattr(obj.Base.Shape,"Length"):
l = obj.Base.Shape.Length
if obj.Length.Value != l:
obj.Length = l
def onChanged(self,obj,prop): def onChanged(self,obj,prop):
self.hideSubobjects(obj,prop) self.hideSubobjects(obj,prop)
ArchComponent.Component.onChanged(self,obj,prop) ArchComponent.Component.onChanged(self,obj,prop)
@ -518,6 +486,114 @@ class _Wall(ArchComponent.Component):
faces.append(f) faces.append(f)
return faces return faces
def getExtrusionData(self,obj):
"""returns (shape,extrusion vector,placement) or None"""
import Part,DraftGeomUtils
data = ArchComponent.Component.getExtrusionData(self,obj)
if data:
return data
length = obj.Length.Value
width = obj.Width.Value
height = obj.Height.Value
if not height:
for p in obj.InList:
if Draft.getType(p) == "Floor":
if p.Height.Value:
height = p.Height.Value
if obj.Normal == Vector(0,0,0):
normal = Vector(0,0,1)
else:
normal = Vector(obj.Normal)
base = None
placement = None
basewires = None
if obj.Base:
if obj.Base.isDerivedFrom("Part::Feature"):
if obj.Base.Shape:
if obj.Base.Shape.Solids:
return None
elif obj.Face > 0:
if len(obj.Base.Shape.Faces) >= obj.Face:
face = obj.Base.Shape.Faces[obj.Face-1]
# this wall is based on a specific face of its base object
normal = face.normalAt(0,0)
if normal.getAngle(Vector(0,0,1)) > math.pi/4:
normal.multiply(width)
base = face.extrude(normal)
if obj.Align == "Center":
base.translate(normal.negative().multiply(0.5))
elif obj.Align == "Right":
base.translate(normal.negative())
else:
normal.multiply(height)
base = face.extrude(normal)
base,placement = self.rebase(base)
return (base,normal,placement)
elif obj.Base.Shape.Faces:
if not DraftGeomUtils.isCoplanar(obj.Base.Shape.Faces):
return None
else:
base,placement = self.rebase(obj.Base.Shape)
elif obj.Base.Shape.Wires:
basewires = obj.Base.Shape.Wires
elif len(obj.Base.Shape.Edges) == 1:
basewires = [Part.Wire(obj.Base.Shape.Edges)]
if basewires and width:
baseface = None
for wire in basewires:
e = wire.Edges[0]
if isinstance(e.Curve,Part.Circle):
dvec = e.Vertexes[0].Point.sub(e.Curve.Center)
else:
dvec = DraftGeomUtils.vec(wire.Edges[0]).cross(normal)
if not DraftVecUtils.isNull(dvec):
dvec.normalize()
sh = None
if obj.Align == "Left":
dvec.multiply(width)
if obj.Offset.Value:
dvec2 = DraftVecUtils.scaleTo(dvec,obj.Offset.Value)
wire = DraftGeomUtils.offsetWire(wire,dvec2)
w2 = DraftGeomUtils.offsetWire(wire,dvec)
w1 = Part.Wire(Part.__sortEdges__(wire.Edges))
sh = DraftGeomUtils.bind(w1,w2)
elif obj.Align == "Right":
dvec.multiply(width)
dvec = dvec.negative()
if obj.Offset.Value:
dvec2 = DraftVecUtils.scaleTo(dvec,obj.Offset.Value)
wire = DraftGeomUtils.offsetWire(wire,dvec2)
w2 = DraftGeomUtils.offsetWire(wire,dvec)
w1 = Part.Wire(Part.__sortEdges__(wire.Edges))
sh = DraftGeomUtils.bind(w1,w2)
elif obj.Align == "Center":
dvec.multiply(width/2)
w1 = DraftGeomUtils.offsetWire(wire,dvec)
dvec = dvec.negative()
w2 = DraftGeomUtils.offsetWire(wire,dvec)
sh = DraftGeomUtils.bind(w1,w2)
if sh:
sh.fix(0.1,0,1) # fixes self-intersecting wires
f = Part.Face(sh)
if baseface:
baseface = baseface.fuse(f)
else:
baseface = f
if baseface:
base,placement = self.rebase(baseface)
else:
l2 = length/2 or 0.5
w2 = width/2 or 0.5
v1 = Vector(-l2,-w2,0)
v2 = Vector(l2,-w2,0)
v3 = Vector(l2,w2,0)
v4 = Vector(-l2,w2,0)
base = Part.Face(Part.makePolygon([v1,v2,v3,v4,v1]))
placement = FreeCAD.Placement()
if base and placement:
extrusion = normal.multiply(height)
return (base,extrusion,placement)
return None
class _ViewProviderWall(ArchComponent.ViewProviderComponent): class _ViewProviderWall(ArchComponent.ViewProviderComponent):
"A View Provider for the Wall object" "A View Provider for the Wall object"

View File

@ -33,18 +33,20 @@ if open.__module__ == '__builtin__':
pyopen = open # because we'll redefine open below pyopen = open # because we'll redefine open below
# which IFC type must create which FreeCAD type # which IFC type must create which FreeCAD type
typesmap = { "Site": ["IfcSite"], typesmap = { "Site": ["IfcSite"],
"Building": ["IfcBuilding"], "Building": ["IfcBuilding"],
"Floor": ["IfcBuildingStorey"], "Floor": ["IfcBuildingStorey"],
"Structure": ["IfcBeam", "IfcBeamStandardCase", "IfcColumn", "IfcColumnStandardCase", "IfcSlab", "IfcFooting", "IfcPile", "IfcTendon"], "Structure": ["IfcBeam", "IfcBeamStandardCase", "IfcColumn", "IfcColumnStandardCase", "IfcSlab", "IfcFooting", "IfcPile", "IfcTendon"],
"Wall": ["IfcWall", "IfcWallStandardCase", "IfcCurtainWall"], "Wall": ["IfcWall", "IfcWallStandardCase", "IfcCurtainWall"],
"Window": ["IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"], "Window": ["IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"],
"Roof": ["IfcRoof"], "Roof": ["IfcRoof"],
"Stairs": ["IfcStair", "IfcStairFlight", "IfcRamp", "IfcRampFlight"], "Stairs": ["IfcStair", "IfcStairFlight", "IfcRamp", "IfcRampFlight"],
"Space": ["IfcSpace"], "Space": ["IfcSpace"],
"Rebar": ["IfcReinforcingBar"], "Rebar": ["IfcReinforcingBar"],
"Panel": ["IfcPlate"], "Panel": ["IfcPlate"],
"Equipment": ["IfcFurnishingElement","IfcSanitaryTerminal","IfcFlowTerminal","IfcElectricAppliance"] "Equipment": ["IfcFurnishingElement","IfcSanitaryTerminal","IfcFlowTerminal","IfcElectricAppliance"],
"Pipe": ["IfcPipeSegment"],
"PipeConnector":["IfcPipeFitting"]
} }
# which IFC entity (product) is a structural object # which IFC entity (product) is a structural object
@ -55,7 +57,7 @@ structuralifcobjects = (
"IfcStructuralLinearAction", "IfcStructuralLinearActionVarying", "IfcStructuralPlanarAction" "IfcStructuralLinearAction", "IfcStructuralLinearActionVarying", "IfcStructuralPlanarAction"
) )
# specific name translations # specific FreeCAD <-> IFC slang translations
translationtable = { "Foundation":"Footing", translationtable = { "Foundation":"Footing",
"Floor":"BuildingStorey", "Floor":"BuildingStorey",
"Rebar":"ReinforcingBar", "Rebar":"ReinforcingBar",
@ -63,7 +65,9 @@ translationtable = { "Foundation":"Footing",
"ElectricEquipment":"ElectricAppliance", "ElectricEquipment":"ElectricAppliance",
"Furniture":"FurnishingElement", "Furniture":"FurnishingElement",
"Stair Flight":"StairFlight", "Stair Flight":"StairFlight",
"Curtain Wall":"CurtainWall" "Curtain Wall":"CurtainWall",
"Pipe Segment":"PipeSegment",
"Pipe Fitting":"PipeFitting"
} }
ifctemplate = """ISO-10303-21; ifctemplate = """ISO-10303-21;
@ -785,7 +789,7 @@ def insert(filename,docname,skip=[],only=[],root=None):
else: else:
if DEBUG: print "no group name specified for entity: #", ifcfile[host].id(), ", entity type is used!" if DEBUG: print "no group name specified for entity: #", ifcfile[host].id(), ", entity type is used!"
grp_name = ifcfile[host].is_a() + "_" + str(ifcfile[host].id()) grp_name = ifcfile[host].is_a() + "_" + str(ifcfile[host].id())
grp = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup",grp_name) grp = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup",grp_name.encode("utf8"))
objects[host] = grp objects[host] = grp
for child in children: for child in children:
if child in objects.keys(): if child in objects.keys():
@ -962,7 +966,7 @@ def export(exportList,filename):
of.write(template.encode("utf8")) of.write(template.encode("utf8"))
of.close() of.close()
os.close(templatefilehandle) os.close(templatefilehandle)
global ifcfile, surfstyles, clones, sharedobjects global ifcfile, surfstyles, clones, sharedobjects, profiledefs, shapedefs
ifcfile = ifcopenshell.open(templatefile) ifcfile = ifcopenshell.open(templatefile)
history = ifcfile.by_type("IfcOwnerHistory")[0] history = ifcfile.by_type("IfcOwnerHistory")[0]
context = ifcfile.by_type("IfcGeometricRepresentationContext")[0] context = ifcfile.by_type("IfcGeometricRepresentationContext")[0]
@ -985,6 +989,8 @@ def export(exportList,filename):
sharedobjects = {} # { BaseName: IfcRepresentationMap } sharedobjects = {} # { BaseName: IfcRepresentationMap }
count = 1 count = 1
groups = {} # { Host: [Child,Child,...] } groups = {} # { Host: [Child,Child,...] }
profiledefs = {} # { ProfileDefString:profiledef,...}
shapedefs = {} # { ShapeDefString:[shapes],... }
# build clones table # build clones table
if CREATE_CLONES: if CREATE_CLONES:
@ -1318,6 +1324,7 @@ def export(exportList,filename):
# 2D objects # 2D objects
if EXPORT_2D: if EXPORT_2D:
curvestyles = {}
if annotations and DEBUG: print "exporting 2D objects..." if annotations and DEBUG: print "exporting 2D objects..."
for anno in annotations: for anno in annotations:
xvc = ifcfile.createIfcDirection((1.0,0.0,0.0)) xvc = ifcfile.createIfcDirection((1.0,0.0,0.0))
@ -1342,6 +1349,22 @@ def export(exportList,filename):
tpl = ifcfile.createIfcAxis2Placement3D(pos,None,None) tpl = ifcfile.createIfcAxis2Placement3D(pos,None,None)
txt = ifcfile.createIfcTextLiteral(";".join(anno.LabelText).encode("utf8"),tpl,"LEFT") txt = ifcfile.createIfcTextLiteral(";".join(anno.LabelText).encode("utf8"),tpl,"LEFT")
reps = [txt] reps = [txt]
for coldef in ["LineColor","TextColor","ShapeColor"]:
if hasattr(obj.ViewObject,coldef):
rgb = getattr(obj.ViewObject,coldef)[:3]
if rgb in curvestyles:
psa = curvestyles[rgb]
else:
col = ifcfile.createIfcColourRgb(None,rgb[0],rgb[1],rgb[2])
cvf = ifcfile.createIfcDraughtingPredefinedCurveFont("CONTINUOUS")
ics = ifcfile.createIfcCurveStyle('Line',cvf,None,col)
psa = ifcfile.createIfcPresentationStyleAssignment([ics])
curvestyles[rgb] = psa
for rep in reps:
isi = ifcfile.createIfcStyledItem(rep,[psa],None)
break
shp = ifcfile.createIfcShapeRepresentation(context,'Annotation','Annotation2D',reps) shp = ifcfile.createIfcShapeRepresentation(context,'Annotation','Annotation2D',reps)
rep = ifcfile.createIfcProductDefinitionShape(None,None,[shp]) rep = ifcfile.createIfcProductDefinitionShape(None,None,[shp])
ann = ifcfile.createIfcAnnotation(ifcopenshell.guid.compress(uuid.uuid1().hex),history,anno.Label.encode('utf8'),'',None,gpl,rep) ann = ifcfile.createIfcAnnotation(ifcopenshell.guid.compress(uuid.uuid1().hex),history,anno.Label.encode('utf8'),'',None,gpl,rep)
@ -1390,7 +1413,7 @@ def createCurve(ifcfile,wire):
if da > 0: if da > 0:
follow = not(follow) follow = not(follow)
xvc = ifcfile.createIfcDirection((1.0,0.0)) xvc = ifcfile.createIfcDirection((1.0,0.0))
ovc = ifcfile.createIfcCartesianPoint(tuple(e.Curve.Center)[:2]) ovc = ifcfile.createIfcCartesianPoint(tuple(e.Curve.Center))
plc = ifcfile.createIfcAxis2Placement2D(ovc,xvc) plc = ifcfile.createIfcAxis2Placement2D(ovc,xvc)
cir = ifcfile.createIfcCircle(plc,e.Curve.Radius) cir = ifcfile.createIfcCircle(plc,e.Curve.Radius)
curve = ifcfile.createIfcTrimmedCurve(cir,[ifcfile.createIfcParameterValue(p1)],[ifcfile.createIfcParameterValue(p2)],follow,"PARAMETER") curve = ifcfile.createIfcTrimmedCurve(cir,[ifcfile.createIfcParameterValue(p1)],[ifcfile.createIfcParameterValue(p2)],follow,"PARAMETER")
@ -1404,7 +1427,7 @@ def createCurve(ifcfile,wire):
last = e.Vertexes[-1].Point last = e.Vertexes[-1].Point
else: else:
last = e.Vertexes[-1].Point last = e.Vertexes[-1].Point
pts = [ifcfile.createIfcCartesianPoint(tuple(v)[:2]) for v in verts] pts = [ifcfile.createIfcCartesianPoint(tuple(v)) for v in verts]
curve = ifcfile.createIfcPolyline(pts) curve = ifcfile.createIfcPolyline(pts)
segment = ifcfile.createIfcCompositeCurveSegment("CONTINUOUS",True,curve) segment = ifcfile.createIfcCompositeCurveSegment("CONTINUOUS",True,curve)
segments.append(segment) segments.append(segment)
@ -1422,6 +1445,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
productdef = None productdef = None
shapetype = "no shape" shapetype = "no shape"
tostore = False tostore = False
subplacement = None
# check for clones # check for clones
if (not subtraction) and (not forcebrep): if (not subtraction) and (not forcebrep):
@ -1447,67 +1471,62 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
if (not shapes) and (not forcebrep): if (not shapes) and (not forcebrep):
profile = None profile = None
if hasattr(obj,"Proxy"): if hasattr(obj,"Proxy"):
if hasattr(obj.Proxy,"getProfiles"): if hasattr(obj.Proxy,"getExtrusionData"):
p = obj.Proxy.getProfiles(obj,noplacement=True) extdata = obj.Proxy.getExtrusionData(obj)
extrusionv = obj.Proxy.getExtrusionVector(obj,noplacement=True) if extdata:
if not DraftVecUtils.isNull(extrusionv): # convert to meters
extrusionv.multiply(0.001) # to meters p = extdata[0]
if (len(p) == 1) and extrusionv: p.scale(0.001)
p = p[0].copy() ev = extdata[1]
p.scale(0.001) # to meters ev.multiply(0.001)
r = obj.Proxy.getPlacement(obj) pl = extdata[2]
r.Base = r.Base.multiply(0.001) # to meters pl.Base = pl.Base.multiply(0.001)
d = DraftGeomUtils.getNormal(p.Wires[0]) pstr = str([v.Point for v in extdata[0].Vertexes])
if r.isNull() and ( (p.CenterOfMass.z > 0.001) or ( (d.getAngle(FreeCAD.Vector(0,0,1)) > 0.001) and (d.getAngle(FreeCAD.Vector(0,0,1)) < 3.14159) ) ): if pstr in profiledefs:
# the object placement is null, but the profile is not in the XY plane. profile = profiledefs[pstr]
npla = FreeCAD.Placement() shapetype = "reusing profile"
npla.Base = p.Vertexes[0].Point else:
nrot = FreeCAD.Rotation(FreeCAD.Vector(0,0,1),d)
npla.Rotation = nrot
r = npla
# p.Placement = p.Placement.multiply(npla.inverse()) # move the profile to origin - not working??
p.translate(p.Vertexes[0].Point.negative())
p.rotate(FreeCAD.Vector(0,0,0),nrot.inverted().Axis,math.degrees(nrot.inverted().Angle))
extrusionv = nrot.inverted().multVec(extrusionv) # move the extrusion vector to Z axis, mandatory in IFC
if len(p.Edges) == 1: if len(p.Edges) == 1:
pxvc = ifcfile.createIfcDirection((1.0,0.0)) pxvc = ifcfile.createIfcDirection((1.0,0.0))
povc = ifcfile.createIfcCartesianPoint((0.0,0.0)) povc = ifcfile.createIfcCartesianPoint((0.0,0.0))
pt = ifcfile.createIfcAxis2Placement2D(povc,pxvc) pt = ifcfile.createIfcAxis2Placement2D(povc,pxvc)
# extruded circle
if isinstance(p.Edges[0].Curve,Part.Circle): if isinstance(p.Edges[0].Curve,Part.Circle):
# extruded circle
profile = ifcfile.createIfcCircleProfileDef("AREA",None,pt, p.Edges[0].Curve.Radius) profile = ifcfile.createIfcCircleProfileDef("AREA",None,pt, p.Edges[0].Curve.Radius)
# extruded ellipse
elif isinstance(p.Edges[0].Curve,Part.Ellipse): elif isinstance(p.Edges[0].Curve,Part.Ellipse):
# extruded ellipse
profile = ifcfile.createIfcEllipseProfileDef("AREA",None,pt, p.Edges[0].Curve.MajorRadius, p.Edges[0].Curve.MinorRadius) profile = ifcfile.createIfcEllipseProfileDef("AREA",None,pt, p.Edges[0].Curve.MajorRadius, p.Edges[0].Curve.MinorRadius)
else: else:
curves = False curves = False
for e in p.Edges: for e in p.Edges:
if isinstance(e.Curve,Part.Circle): if isinstance(e.Curve,Part.Circle):
curves = True curves = True
# extruded polyline
if not curves: if not curves:
w = Part.Wire(Part.__sortEdges__(p.Edges)) # extruded polyline
w = Part.Wire(Part.__sortEdges__(p.Wires[0].Edges))
pts = [ifcfile.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]] pts = [ifcfile.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]]
pol = ifcfile.createIfcPolyline(pts) pol = ifcfile.createIfcPolyline(pts)
# extruded composite curve
else: else:
# extruded composite curve
pol = createCurve(ifcfile,p) pol = createCurve(ifcfile,p)
profile = ifcfile.createIfcArbitraryClosedProfileDef("AREA",None,pol) profile = ifcfile.createIfcArbitraryClosedProfileDef("AREA",None,pol)
if profile:
profiledefs[pstr] = profile
if profile and not(DraftVecUtils.isNull(extrusionv)): if profile and not(DraftVecUtils.isNull(ev)):
xvc = ifcfile.createIfcDirection(tuple(r.Rotation.multVec(FreeCAD.Vector(1,0,0)))) #ev = pl.Rotation.inverted().multVec(ev)
zvc = ifcfile.createIfcDirection(tuple(r.Rotation.multVec(FreeCAD.Vector(0,0,1)))) #print "ev:",ev
ovc = ifcfile.createIfcCartesianPoint(tuple(r.Base)) if not tostore:
# add the object placement to the profile placement. Otherwise it'll be done later at map insert
pl2 = FreeCAD.Placement(obj.Placement)
pl2.Base = pl2.Base.multiply(0.001)
pl = pl2.multiply(pl)
xvc = ifcfile.createIfcDirection(tuple(pl.Rotation.multVec(FreeCAD.Vector(1,0,0))))
zvc = ifcfile.createIfcDirection(tuple(pl.Rotation.multVec(FreeCAD.Vector(0,0,1))))
ovc = ifcfile.createIfcCartesianPoint(tuple(pl.Base))
lpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc) lpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc)
edir = ifcfile.createIfcDirection(tuple(FreeCAD.Vector(extrusionv).normalize())) edir = ifcfile.createIfcDirection(tuple(FreeCAD.Vector(ev).normalize()))
shape = ifcfile.createIfcExtrudedAreaSolid(profile,lpl,edir,extrusionv.Length) shape = ifcfile.createIfcExtrudedAreaSolid(profile,lpl,edir,ev.Length)
shapes.append(shape) shapes.append(shape)
solidType = "SweptSolid" solidType = "SweptSolid"
shapetype = "extrusion" shapetype = "extrusion"
@ -1521,113 +1540,131 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
if hasattr(obj.Proxy,"getSubVolume"): if hasattr(obj.Proxy,"getSubVolume"):
fcshape = obj.Proxy.getSubVolume(obj) fcshape = obj.Proxy.getSubVolume(obj)
if not fcshape: if not fcshape:
if hasattr(obj,"Shape"): if obj.isDerivedFrom("Part::Feature"):
if obj.Shape: if False: # below is buggy. No way to duplicate shapes that way?
if not obj.Shape.isNull(): #if hasattr(obj,"Base") and hasattr(obj,"Additions")and hasattr(obj,"Subtractions"):
fcshape = obj.Shape if obj.Base and (not obj.Additions) and not(obj.Subtractions):
elif hasattr(obj,"Terrain"): if obj.Base.isDerivedFrom("Part::Feature"):
if obj.Terrain: if obj.Base.Shape:
if hasattr(obj.Terrain,"Shape"): if obj.Base.Shape.Solids:
if obj.Terrain.Shape: fcshape = obj.Base.Shape
if not obj.Terrain.Shape.isNull(): subplacement = FreeCAD.Placement(obj.Placement)
fcshape = obj.Terrain.Shape if not fcshape:
if obj.Shape:
if not obj.Shape.isNull():
fcshape = obj.Shape
if fcshape: if fcshape:
solids = [] shapedef = str([v.Point for v in fcshape.Vertexes])
if fcshape.Solids: if shapedef in shapedefs:
dataset = fcshape.Solids shapes = shapedefs[shapedef]
shapetype = "reusing brep"
else: else:
dataset = fcshape.Shells # new ifcopenshell serializer
if DEBUG: print "Warning! object contains no solids" from ifcopenshell import geom
serialized = False
if hasattr(geom,"serialise") and obj.isDerivedFrom("Part::Feature") and SERIALIZE:
if obj.Shape.Faces:
sh = obj.Shape.copy()
sh.scale(0.001) # to meters
p = geom.serialise(sh.exportBrepToString())
if p:
productdef = ifcfile.add(p)
for rep in productdef.Representations:
rep.ContextOfItems = context
xvc = ifcfile.createIfcDirection((1.0,0.0,0.0))
zvc = ifcfile.createIfcDirection((0.0,0.0,1.0))
ovc = ifcfile.createIfcCartesianPoint((0.0,0.0,0.0))
gpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc)
placement = ifcfile.createIfcLocalPlacement(None,gpl)
shapetype = "advancedbrep"
shapes = None
serialized = True
if not serialized:
# old method
solids = []
if fcshape.Solids:
dataset = fcshape.Solids
else:
dataset = fcshape.Shells
#if DEBUG: print "Warning! object contains no solids"
# if this is a clone, place back the shapes in null position # if this is a clone, place back the shapes in null position
if tostore: if tostore:
for shape in dataset: for shape in dataset:
shape.Placement = FreeCAD.Placement() shape.Placement = FreeCAD.Placement()
# new ifcopenshell serializer for fcsolid in dataset:
from ifcopenshell import geom fcsolid.scale(0.001) # to meters
if hasattr(geom,"serialise") and obj.isDerivedFrom("Part::Feature") and SERIALIZE: faces = []
p = geom.serialise(obj.Shape.exportBrepToString()) curves = False
productdef = ifcfile.add(p) shapetype = "brep"
for rep in productdef.Representations: for fcface in fcsolid.Faces:
rep.ContextOfItems = context for e in fcface.Edges:
xvc = ifcfile.createIfcDirection((1.0,0.0,0.0)) if DraftGeomUtils.geomType(e) != "Line":
zvc = ifcfile.createIfcDirection((0.0,0.0,1.0)) try:
ovc = ifcfile.createIfcCartesianPoint((0.0,0.0,0.0)) if e.curvatureAt(e.FirstParameter+(e.LastParameter-e.FirstParameter)/2) > 0.0001:
gpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc) curves = True
placement = ifcfile.createIfcLocalPlacement(None,gpl) break
shapetype = "advancedbrep" except Part.OCCError:
shapes = None pass
else: if curves:
# old method joinfacets = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcJoinCoplanarFacets",False)
for fcsolid in dataset: usedae = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcUseDaeOptions",False)
fcsolid.scale(0.001) # to meters if joinfacets:
faces = [] result = Arch.removeCurves(fcsolid,dae=usedae)
curves = False if result:
shapetype = "brep" fcsolid = result
for fcface in fcsolid.Faces: else:
for e in fcface.Edges: # fall back to standard triangulation
if DraftGeomUtils.geomType(e) != "Line": joinfacets = False
try: if not joinfacets:
if e.curvatureAt(e.FirstParameter+(e.LastParameter-e.FirstParameter)/2) > 0.0001: shapetype = "triangulated"
curves = True if usedae:
break import importDAE
except Part.OCCError: tris = importDAE.triangulate(fcsolid)
pass else:
if curves: tris = fcsolid.tessellate(tessellation)
joinfacets = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcJoinCoplanarFacets",False) for tri in tris[1]:
usedae = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcUseDaeOptions",False) pts = [ifcfile.createIfcCartesianPoint(tuple(tris[0][i])) for i in tri]
if not joinfacets: loop = ifcfile.createIfcPolyLoop(pts)
shapetype = "triangulated" bound = ifcfile.createIfcFaceOuterBound(loop,True)
if usedae: face = ifcfile.createIfcFace([bound])
import importDAE faces.append(face)
tris = importDAE.triangulate(fcsolid) fcsolid = Part.Shape() # empty shape so below code is not executed
else:
tris = fcsolid.tessellate(tessellation)
for tri in tris[1]:
pts = [ifcfile.createIfcCartesianPoint(tuple(tris[0][i])) for i in tri]
loop = ifcfile.createIfcPolyLoop(pts)
bound = ifcfile.createIfcFaceOuterBound(loop,True)
face = ifcfile.createIfcFace([bound])
faces.append(face)
fcsolid = Part.Shape() # empty shape so below code is not executed
else:
fcsolid = Arch.removeCurves(fcsolid,dae=usedae)
if not fcsolid:
if DEBUG: print "Error: Unable to triangulate shape"
fcsolid = Part.Shape()
for fcface in fcsolid.Faces: for fcface in fcsolid.Faces:
loops = [] loops = []
verts = [v.Point for v in Part.Wire(Part.__sortEdges__(fcface.OuterWire.Edges)).Vertexes] verts = [v.Point for v in fcface.OuterWire.OrderedVertexes]
c = fcface.CenterOfMass c = fcface.CenterOfMass
v1 = verts[0].sub(c) v1 = verts[0].sub(c)
v2 = verts[1].sub(c) v2 = verts[1].sub(c)
n = fcface.normalAt(0,0) n = fcface.normalAt(0,0)
if DraftVecUtils.angle(v2,v1,n) >= 0: if DraftVecUtils.angle(v2,v1,n) >= 0:
verts.reverse() # inverting verts order if the direction is couterclockwise verts.reverse() # inverting verts order if the direction is couterclockwise
pts = [ifcfile.createIfcCartesianPoint(tuple(v)) for v in verts] pts = [ifcfile.createIfcCartesianPoint(tuple(v)) for v in verts]
loop = ifcfile.createIfcPolyLoop(pts) loop = ifcfile.createIfcPolyLoop(pts)
bound = ifcfile.createIfcFaceOuterBound(loop,True) bound = ifcfile.createIfcFaceOuterBound(loop,True)
loops.append(bound) loops.append(bound)
for wire in fcface.Wires: for wire in fcface.Wires:
if wire.hashCode() != fcface.OuterWire.hashCode(): if wire.hashCode() != fcface.OuterWire.hashCode():
verts = [v.Point for v in Part.Wire(Part.__sortEdges__(wire.Edges)).Vertexes] verts = [v.Point for v in wire.OrderedVertexes]
v1 = verts[0].sub(c) v1 = verts[0].sub(c)
v2 = verts[1].sub(c) v2 = verts[1].sub(c)
if DraftVecUtils.angle(v2,v1,DraftVecUtils.neg(n)) >= 0: if DraftVecUtils.angle(v2,v1,DraftVecUtils.neg(n)) >= 0:
verts.reverse() verts.reverse()
pts = [ifcfile.createIfcCartesianPoint(tuple(v)) for v in verts] pts = [ifcfile.createIfcCartesianPoint(tuple(v)) for v in verts]
loop = ifcfile.createIfcPolyLoop(pts) loop = ifcfile.createIfcPolyLoop(pts)
bound = ifcfile.createIfcFaceBound(loop,True) bound = ifcfile.createIfcFaceBound(loop,True)
loops.append(bound) loops.append(bound)
face = ifcfile.createIfcFace(loops) face = ifcfile.createIfcFace(loops)
faces.append(face) faces.append(face)
if faces: if faces:
shell = ifcfile.createIfcClosedShell(faces) shell = ifcfile.createIfcClosedShell(faces)
shape = ifcfile.createIfcFacetedBrep(shell) shape = ifcfile.createIfcFacetedBrep(shell)
shapes.append(shape) shapes.append(shape)
shapedefs[shapedef] = shapes
if shapes: if shapes:
@ -1638,7 +1675,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
ovc = ifcfile.createIfcCartesianPoint((0.0,0.0,0.0)) ovc = ifcfile.createIfcCartesianPoint((0.0,0.0,0.0))
gpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc) gpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc)
repmap = ifcfile.createIfcRepresentationMap(gpl,subrep) repmap = ifcfile.createIfcRepresentationMap(gpl,subrep)
pla = FreeCAD.ActiveDocument.getObject(tostore).Placement pla = obj.Placement
axis1 = ifcfile.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(1,0,0)))) axis1 = ifcfile.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(1,0,0))))
axis2 = ifcfile.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,1,0)))) axis2 = ifcfile.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,1,0))))
origin = ifcfile.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(0.001))) origin = ifcfile.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(0.001)))