File size: 16,968 Bytes
985c397 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 | # SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2022 Yorik van Havre <yorik@uncreated.net> *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD 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 *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
"""This module contains IFC object definitions"""
import FreeCAD
import FreeCADGui
translate = FreeCAD.Qt.translate
# the property groups below should not be treated as psets
NON_PSETS = [
"Base",
"IFC",
"",
"Geometry",
"Dimension",
"Linear/radial dimension",
"SectionPlane",
"Axis",
"PhysicalProperties",
"BuildingPart",
"IFC Attributes",
]
class ifc_object:
"""Base class for all IFC-based objects"""
def __init__(self, otype=None):
self.cached = True # this marks that the object is freshly created and its shape should be taken from cache
self.virgin_placement = True # this allows one to set the initial placement without triggering any placement change
if otype:
self.Type = otype[0].upper() + otype[1:] # capitalize to match Draft standard
else:
self.Type = "IfcObject"
def onBeforeChange(self, obj, prop):
if prop == "Schema":
self.old_schema = obj.Schema
elif prop == "Placement":
self.old_placement = obj.Placement
def onChanged(self, obj, prop):
# link class property to its hidder IfcClass counterpart
if prop == "IfcClass" and hasattr(obj, "Class") and obj.Class != obj.IfcClass:
obj.Class = obj.IfcClass
self.rebuild_classlist(obj, setprops=True)
elif prop == "Class" and hasattr(obj, "IfcClass") and obj.Class != obj.IfcClass:
obj.IfcClass = obj.Class
self.rebuild_classlist(obj, setprops=True)
elif prop == "Schema":
self.edit_schema(obj, obj.Schema)
elif prop == "Type":
self.edit_type(obj)
self.assign_classification(obj)
elif prop == "Classification":
self.edit_classification(obj)
elif prop == "Group":
self.edit_group(obj)
elif hasattr(obj, prop) and obj.getGroupOfProperty(prop) == "IFC":
if prop not in ["StepId"]:
self.edit_attribute(obj, prop)
elif prop == "Label":
self.edit_attribute(obj, "Name", obj.Label)
elif prop == "Text":
self.edit_annotation(obj, "Text", "\n".join(obj.Text))
elif prop in ["Start", "End"]:
self.edit_annotation(obj, prop)
elif prop in ["DisplayLength", "DisplayHeight", "Depth"]:
self.edit_annotation(obj, prop)
elif prop == "Placement":
if getattr(self, "virgin_placement", False):
self.virgin_placement = False
elif obj.Placement != getattr(self, "old_placement", None):
# print("placement changed for",obj.Label,"to",obj.Placement)
self.edit_placement(obj)
elif prop == "Modified":
if obj.ViewObject:
obj.ViewObject.signalChangeIcon()
elif hasattr(obj, prop) and obj.getGroupOfProperty(prop) == "Geometry":
self.edit_geometry(obj, prop)
elif hasattr(obj, prop) and obj.getGroupOfProperty(prop) == "Quantities":
self.edit_quantity(obj, prop)
elif hasattr(obj, prop) and obj.getGroupOfProperty(prop) not in NON_PSETS:
# Treat all property groups outside the default ones as Psets
# print("DEBUG: editinog pset prop",prop)
self.edit_pset(obj, prop)
def onDocumentRestored(self, obj):
self.rebuild_classlist(obj)
if hasattr(obj, "IfcFilePath"):
# once we have loaded the project, recalculate child coin nodes
from PySide import QtCore # lazy loading
if obj.OutListRecursive:
for child in obj.OutListRecursive:
if getattr(child, "ShapeMode", None) == "Coin":
child.Proxy.cached = True
child.touch()
else:
obj.Proxy.cached = True
QtCore.QTimer.singleShot(100, obj.touch)
QtCore.QTimer.singleShot(100, obj.Document.recompute)
QtCore.QTimer.singleShot(100, self.fit_all)
def assign_classification(self, obj):
"""
Assigns Classification to an IFC object in a case where
the object references a Type that has a Classification property,
so we move copy the Type's property to our actual object.
"""
if not getattr(obj, "Type", None):
return
type_obj = obj.Type
if getattr(type_obj, "Classification", None):
# Check if there is Classification already, since user can just change
# the IFC type, but there could be one previously assigned which had
# Classification
if getattr(obj, "Classification", None) is None:
obj.addProperty("App::PropertyString", "Classification", "IFC")
obj.Classification = type_obj.Classification
obj.recompute()
elif getattr(obj, "Classification", None):
# This means user has assigned type that has no classification, so clear
# the one that they have currently selected
obj.Classification = ""
obj.recompute()
def fit_all(self):
"""Fits the view"""
if FreeCAD.GuiUp:
FreeCADGui.SendMsgToActiveView("ViewFit")
def rebuild_classlist(self, obj, setprops=False):
"""rebuilds the list of Class enum property according to current class"""
from . import ifc_tools # lazy import
obj.Class = [obj.IfcClass]
obj.Class = ifc_tools.get_ifc_classes(obj, obj.IfcClass)
obj.Class = obj.IfcClass
if setprops:
ifc_tools.remove_unused_properties(obj)
ifc_tools.add_properties(obj)
def __getstate__(self):
return getattr(self, "Type", None)
def __setstate__(self, state):
self.loads(state)
def dumps(self):
return getattr(self, "Type", None)
def loads(self, state):
if state:
self.Type = state
def execute(self, obj):
from . import ifc_generator # lazy import
if obj.isDerivedFrom("Part::Feature"):
cached = getattr(self, "cached", False)
ifc_generator.generate_geometry(obj, cached=cached)
self.cached = False
self.rebuild_classlist(obj)
def addObject(self, obj, child):
if child not in obj.Group:
g = obj.Group
g.append(child)
obj.Group = g
def removeObject(self, obj, child):
if child in obj.Group:
g = obj.Group
g.remove(child)
obj.Group = g
def edit_attribute(self, obj, attribute, value=None):
"""Edits an attribute of an underlying IFC object"""
from . import ifc_tools # lazy import
if not value:
value = obj.getPropertyByName(attribute)
ifcfile = ifc_tools.get_ifcfile(obj)
elt = ifc_tools.get_ifc_element(obj, ifcfile)
if elt:
result = ifc_tools.set_attribute(ifcfile, elt, attribute, value)
if result:
if hasattr(result, "id") and (result.id() != obj.StepId):
obj.StepId = result.id()
def edit_annotation(self, obj, attribute, value=None):
"""Edits an attribute of an underlying IFC annotation"""
from . import ifc_tools # lazy import
from . import ifc_export
if not value:
if hasattr(obj, attribute):
value = obj.getPropertyByName(attribute)
ifcfile = ifc_tools.get_ifcfile(obj)
elt = ifc_tools.get_ifc_element(obj, ifcfile)
if elt:
if attribute == "Text":
text = ifc_export.get_text(elt)
if text:
ifc_tools.set_attribute(ifcfile, text, "Literal", value)
elif attribute in ["Start", "End"]:
dim = ifc_export.get_dimension(elt)
if dim:
rep = dim[0]
for curve in rep.Items:
if not hasattr(curve, "Elements"):
# this is a TextLiteral for the dimension text - skip it
continue
for sub in curve.Elements:
if sub.is_a("IfcIndexedPolyCurve"):
points = sub.Points
value = list(points.CoordList)
is2d = "2D" in points.is_a()
if attribute == "Start":
value[0] = ifc_export.get_scaled_point(obj.Start, ifcfile, is2d)
else:
value[-1] = ifc_export.get_scaled_point(obj.End, ifcfile, is2d)
ifc_tools.set_attribute(ifcfile, points, "CoordList", value)
else:
print("DEBUG: unknown dimension curve type:", sub)
elif attribute in ["DisplayLength", "DisplayHeight", "Depth"]:
l = w = h = 1000.0
if obj.ViewObject:
if obj.ViewObject.DisplayLength.Value:
l = ifc_export.get_scaled_value(obj.ViewObject.DisplayLength.Value, ifcfile)
if obj.ViewObject.DisplayHeight.Value:
w = ifc_export.get_scaled_value(obj.ViewObject.DisplayHeight.Value, ifcfile)
if obj.Depth.Value:
h = ifc_export.get_scaled_value(obj.Depth.Value, ifcfile)
if elt.Representation.Representations:
for rep in elt.Representation.Representations:
for item in rep.Items:
if item.is_a("IfcCsgSolid"):
if item.TreeRootExpression.is_a("IfcBlock"):
block = item.TreeRootExpression
loc = block.Position.Location
ifc_tools.set_attribute(ifcfile, block, "XLength", l)
ifc_tools.set_attribute(ifcfile, block, "YLength", w)
ifc_tools.set_attribute(ifcfile, block, "ZLength", h)
ifc_tools.set_attribute(
ifcfile, loc, "Coordinates", (-l / 2, -h / 2, -h)
)
def edit_geometry(self, obj, prop):
"""Edits a geometry property of an object"""
from . import ifc_geometry # lazy loading
from . import ifc_tools # lazy import
result = ifc_geometry.set_geom_property(obj, prop)
if result:
obj.touch()
def edit_schema(self, obj, schema):
"""Changes the schema of an IFC document"""
from . import ifc_tools # lazy import
ifcfile = ifc_tools.get_ifcfile(obj)
if not ifcfile:
return
if not getattr(self, "old_schema", None):
return
if schema != ifcfile.wrapped_data.schema_name():
# set obj.Proxy.silent = True to disable the schema change warning
if obj.ViewObject and not getattr(self, "silent", False):
if not obj.ViewObject.Proxy.schema_warning():
return
ifcfile, migration_table = ifc_tools.migrate_schema(ifcfile, schema)
self.ifcfile = ifcfile
for old_id, new_id in migration_table.items():
child = [o for o in obj.OutListRecursive if getattr(o, "StepId", None) == old_id]
if len(child) == 1:
child[0].StepId = new_id
def edit_placement(self, obj):
"""Syncs the internal IFC placement"""
from . import ifc_tools # lazy import
ifc_tools.set_placement(obj)
def edit_pset(self, obj, prop):
"""Edits a Pset value"""
from . import ifc_psets # lazy import
ifc_psets.edit_pset(obj, prop)
def edit_group(self, obj):
"""Edits the children list"""
from . import ifc_tools # lazy import
from . import ifc_layers
if obj.Class in [
"IfcPresentationLayerAssignment",
"IfcPresentationLayerWithStyle",
]:
ifcfile = ifc_tools.get_ifcfile(obj)
if not ifcfile:
return
newlist = []
for child in obj.Group:
if not getattr(child, "StepId", None) or ifc_tools.get_ifcfile(child) != ifcfile:
print(
"DEBUG: Not an IFC object. Removing",
child.Label,
"from layer",
obj.Label,
)
else:
# print("DEBUG: adding", child.Label, "to layer", obj.Label)
newlist.append(child)
ifc_layers.add_to_layer(child, obj)
if newlist != obj.Group:
obj.Group = newlist
def edit_type(self, obj):
"""Edits the type of this object"""
from . import ifc_types # lazy import
ifc_types.edit_type(obj)
def edit_quantity(self, obj, prop):
"""Edits the given quantity"""
pass # TODO implement
def get_section_data(self, obj):
"""Returns two things: a list of objects and a cut plane"""
from . import ifc_tools # lazy import
import Part
if not obj.IfcClass == "IfcAnnotation":
return [], None
if obj.ObjectType != "DRAWING":
return [], None
objs = getattr(obj, "Objects", [])
if not objs:
# no object defined, we automatically use the project
objs = []
proj = ifc_tools.get_project(obj)
if isinstance(proj, FreeCAD.DocumentObject):
objs.append(proj)
objs.extend(ifc_tools.get_freecad_children(proj))
if objs:
s = []
for o in objs:
# TODO print a better message
if o.ShapeMode != "Shape":
s.append(o)
if s:
FreeCAD.Console.PrintLog("DEBUG: Generating shapes. This might take some time...\n")
for o in s:
o.ShapeMode = "Shape"
o.recompute()
l = 1
h = 1
if obj.ViewObject:
if hasattr(obj.ViewObject, "DisplayLength"):
l = obj.ViewObject.DisplayLength.Value
h = obj.ViewObject.DisplayHeight.Value
plane = Part.makePlane(l, h, FreeCAD.Vector(l / 2, -h / 2, 0), FreeCAD.Vector(0, 0, 1))
plane.Placement = obj.Placement
return objs, plane
else:
print("DEBUG: Section plane returned no objects")
return [], None
def edit_classification(self, obj):
"""Edits the classification of this object"""
from . import ifc_classification # lazy loading
ifc_classification.edit_classification(obj)
class document_object:
"""Holder for the document's IFC objects"""
def __init__(self):
pass
|