[ArchWindow] Clone and Link to Support Sill Change

Problems / Course of Developement:

  1. Allow changing Sill feature was added in
  - https://github.com/FreeCAD/FreeCAD/pull/21005

  2. Bug fix was added in
  - f6bba5d58e

  3. Further Problems were identified as discused in
  - https://github.com/FreeCAD/FreeCAD/pull/21005#issuecomment-3010959162

  'New Observation-

      1. Clone or Link to a Window does not work and return error
      2. This PR/Commit (21005) translate the Base Sketch when Sill property is changed, to change the final disposition of a Window object; however, it will also shift the disposition of a Clone and Link as a result (with error message above fixed). This is apparently not an expected behaviour.

  (Sidenote- When SketchArch Addon is installed and parametric placement is used, there would not no impact)

Current Commit:

  1. Support Link and Clone of Arch Windows (bug fix)

  2. Instead of changing base Sketch disposition, the Placement of Arch Window, Clone, or Link is adjusted.
This commit is contained in:
Paul Lee
2025-08-11 00:26:54 +08:00
committed by Yorik van Havre
parent 76f2b120ff
commit 67178b953d

View File

@@ -106,10 +106,6 @@ class _Window(ArchComponent.Component):
# Add features in the SketchArch External Add-on
self.addSketchArchFeatures(obj)
# Initialize those two values later on during first onChanged call
self.baseSill = None
self.basePos = None
def addSketchArchFeatures(self,obj,linkObj=None,mode=None):
'''
To add features in the SketchArch External Add-on (https://github.com/paullee0/FreeCAD_SketchArch)
@@ -142,8 +138,6 @@ class _Window(ArchComponent.Component):
obj.addProperty("App::PropertyLength","Width","Window",QT_TRANSLATE_NOOP("App::Property","The width of this window"), locked=True)
if not "Height" in lp:
obj.addProperty("App::PropertyLength","Height","Window",QT_TRANSLATE_NOOP("App::Property","The height of this window"), locked=True)
if not "Sill" in lp:
obj.addProperty("App::PropertyLength","Sill","Window",QT_TRANSLATE_NOOP("App::Property","The height of this window's sill"), locked=True)
if not "Normal" in lp:
obj.addProperty("App::PropertyVector","Normal","Window",QT_TRANSLATE_NOOP("App::Property","The normal direction of this window"), locked=True)
# Automatic Normal Reverse
@@ -180,6 +174,31 @@ class _Window(ArchComponent.Component):
obj.setEditorMode("HorizontalArea",2)
obj.setEditorMode("PerimeterLength",2)
# Sill change related properies
self.setSillProperties(obj)
def setSillProperties(self, orgObj, linkObj=None):
''' Set properties which support Sill change.
Support both Arch Window and Link of Arch Window.
'''
if linkObj:
obj = linkObj
else:
obj = orgObj
prop = obj.PropertiesList
# 'Sill' support
if not "Sill" in prop:
obj.addProperty("App::PropertyLength","Sill","Window",QT_TRANSLATE_NOOP("App::Property","The height of this window's sill"), locked=True)
# Link has no Proxy, so needs to use PropertyPythonObject
sillProp = ['baseSill','basePosZ','atthOffZ']
for i in sillProp:
if i not in prop:
obj.addProperty("App::PropertyPythonObject", i)
def onDocumentRestored(self,obj):
ArchComponent.Component.onDocumentRestored(self,obj)
@@ -188,12 +207,13 @@ class _Window(ArchComponent.Component):
# Add features in the SketchArch External Add-on
self.addSketchArchFeatures(obj, mode='ODR')
# TODO 2025.6.27 : Seems Sill already triggered onChanged() upon document restored - NO need codes below in onDocumentRestored()
# Need to restore 'initial' settings as corresponding codes in onChanged() does upon object creation
self.baseSill = obj.Sill.Value
self.basePos = obj.Base.Placement.Base
self.atthOff = None
if hasattr(obj, 'AttachmentOffsetXyzAndRotation'):
self.atthOff = obj.AttachmentOffsetXyzAndRotation.Base
#self.baseSill = obj.Sill.Value
#self.basePos = obj.Base.Placement.Base
#self.atthOff = None
#if hasattr(obj, 'AttachmentOffsetXyzAndRotation'):
# self.atthOff = obj.AttachmentOffsetXyzAndRotation.Base
def loads(self,state):
@@ -210,37 +230,9 @@ class _Window(ArchComponent.Component):
self.hideSubobjects(obj,prop)
if prop == "Sill":
val = getattr(obj,prop).Value
if (getattr(self, 'baseSill', None) is None and
getattr(self, 'basePos', None) is None and
getattr(self, 'atthOff', None) is None): # TODO Any cases only 1 or 2 are not None?
self.baseSill = val
self.basePos = obj.Base.Placement.Base
self.atthOff = None
if hasattr(obj, 'AttachmentOffsetXyzAndRotation'):
self.atthOff = obj.AttachmentOffsetXyzAndRotation.Base
return
import ArchSketchObject # Need to import per method
host = None
if obj.Hosts:
host = obj.Hosts[0]
if (hasattr(obj, 'AttachToAxisOrSketch') and
obj.AttachToAxisOrSketch == "Host" and
host and Draft.getType(host.Base) == "ArchSketch" and
hasattr(ArchSketchObject, 'updateAttachmentOffset')):
SketchArch = True
else:
SketchArch = False
if SketchArch:
objAttOff = obj.AttachmentOffsetXyzAndRotation
objAttOff.Base.z = self.atthOff.z + (obj.Sill.Value - self.baseSill)
obj.AttachmentOffsetXyzAndRotation = objAttOff
else:
obj.Base.Placement.Base.z = self.basePos.z + (obj.Sill.Value - self.baseSill)
self.setSillProperties(obj) # Can't wait until onDocumentRestored
self.onSillChanged(obj)
elif not "Restore" in obj.State:
if prop in ["Base","WindowParts","Placement","HoleDepth","Height","Width","Hosts","Shape"]:
# anti-recursive loops, bc the base sketch will touch the Placement all the time
@@ -574,12 +566,66 @@ class _Window(ArchComponent.Component):
@realthunder added support to Links to run Linked Scripted Object's methods()
'''
# Sill change support
self.setSillProperties(obj, linkObj)
# Add features in the SketchArch External Add-on
self.addSketchArchFeatures(obj, linkObj)
# Execute features in the SketchArch External Add-on
self.executeSketchArchFeatures(obj, linkObj)
# Sill change feature
self.onSillChanged(obj, linkObj)
def onSillChanged(self, orgObj, linkObj=None, index=None, linkElement=None):
if linkObj:
obj = linkObj
else:
obj = orgObj
val = getattr(obj,'Sill').Value
if (getattr(obj, 'baseSill', None) is None and
getattr(obj, 'basePosZ', None) is None and
getattr(obj, 'atthOffZ', None) is None): # TODO Any cases only 1 or 2 are not None?
obj.baseSill = val
# Not to change Base's Placement, would change all Clones and
# Link's disposition unexpectedly to users, undesirable.
#
# self.basePos = obj.Base.Placement.Base
obj.basePosZ = obj.Placement.Base.z
obj.atthOffZ = None
if hasattr(obj, 'AttachmentOffsetXyzAndRotation'):
obj.atthOffZ = obj.AttachmentOffsetXyzAndRotation.Base.z
return
import ArchSketchObject # Need to import per method
host = None
if obj.Hosts:
host = obj.Hosts[0]
if (hasattr(obj, 'AttachToAxisOrSketch') and
obj.AttachToAxisOrSketch == "Host" and
host and Draft.getType(host.Base) == "ArchSketch" and
hasattr(ArchSketchObject, 'updateAttachmentOffset')):
SketchArch = True
else:
SketchArch = False
# Keep track of change whether SketchArch is True or False (i.e.
# even Window object is not currently parametrically attached to
# a Wall or other Arch object at the moment).
#
# SketchArch or Not
if hasattr(obj, 'AttachmentOffsetXyzAndRotation'):
objAttOff = obj.AttachmentOffsetXyzAndRotation
objAttOff.Base.z = obj.atthOffZ + (obj.Sill.Value - obj.baseSill)
obj.AttachmentOffsetXyzAndRotation = objAttOff
if not SketchArch:
# Not to change Base's Placement
#obj.Base.Placement.Base.z = self.basePos.z + (obj.Sill.Value - self.baseSill)
obj.Placement.Base.z = obj.basePosZ + (obj.Sill.Value - obj.baseSill)
def getSubFace(self):
"returns a subface for creation of subvolume for cutting in a base object"
# creation of subface from HoleWire (getSubWire)