New Macro " Sketch Constraint From Spreadsheet"
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Be nice to others! Respect the FreeCAD code of conduct!
New Macro " Sketch Constraint From Spreadsheet"
Hello, a new macro here :
https://wiki.freecad.org/Macro_Sketch_C ... preadsheet
Discussion in French in the forum : viewtopic.php?t=75972
Macro which, with a simple click on a spreadsheet cell, adds a length constraint to a line or between 2 points using a spreadsheet cell alias or address (ex. C2). Future changes to the spreadsheet will update the constraint. Macro can create alias for you.
Just select 1 line, 2 points or a constraint, click on a spreadsheet cell and run the macro. You can select lines, points at the ends of a line, points, circle, arc of circle.
If we run the macro when we have not yet created a spreadsheet, it suggests to create one and opens it so that we can start filling it in.
It is a beta version, if you use it, thanks to tell me if it is ok for you.
https://wiki.freecad.org/Macro_Sketch_C ... preadsheet
Discussion in French in the forum : viewtopic.php?t=75972
Macro which, with a simple click on a spreadsheet cell, adds a length constraint to a line or between 2 points using a spreadsheet cell alias or address (ex. C2). Future changes to the spreadsheet will update the constraint. Macro can create alias for you.
Just select 1 line, 2 points or a constraint, click on a spreadsheet cell and run the macro. You can select lines, points at the ends of a line, points, circle, arc of circle.
If we run the macro when we have not yet created a spreadsheet, it suggests to create one and opens it so that we can start filling it in.
It is a beta version, if you use it, thanks to tell me if it is ok for you.
Last edited by 2cv001 on Tue Mar 21, 2023 5:01 pm, edited 2 times in total.
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
Re: New Macro
Please consider renaming the topic to something more descriptive.
A Sketcher Lecture with in-depth information is available in English, auf Deutsch, en français, en español.
Re: New Macro
Just reading the code without being at a PC to use it yet, are you going to make it all true English and then apply Translate to the text elements, at the moment the 'Franglais' is really off-putting? I'll test it on a English(UK) Windows box later as I suspect some of the characters will cause some errors.
Re: New Macro
Done. Thanks.
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
Re: New Macro " Sketch Constraint From Spreadsheet"
Good, that makes it available to interested users and search engines.
A Sketcher Lecture with in-depth information is available in English, auf Deutsch, en français, en español.
Re: New Macro " Sketch Constraint From Spreadsheet"
As I anticipated I got an error :
Therefore below is the macro after my modifications to stop the error, improve the macro folder lookup and make readability improvements.
IMHO there are still improvements to stop the ini file not found repeatedly appearing in the Report View but it does what it says on the tin.
Tested using:
Code: Select all
20:11:37 <class 'SyntaxError'>: ("(unicode error) 'utf-8' codec can't decode byte 0xe9 in position 411: invalid continuation byte", ('E:/Data/FreeCAD/Macro/SketchConstraintFromSpreadsheet.FCMacro', 42, 1, "'''\n"))
Code: Select all
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# =========================================================================
# Macro Sketch Constraint From Spreadsheet
# file name : sketchConstraintFromSpreadsheet.FCMacro
# ========================================================================
# == ==
# == Adds a length constraint to a line or between 2 points ==
# == using a spreadsheet cell alias or name (ex. C2). ==
# == Future changes to the spreadsheet will update the constraint. ==
# == if necessary, the macro help you to create alias ==
# == USE: ==
# == 1) Select 1 line, 2 points or a constraint ==
# == 2) Click on a spreadsheet cell ==
# == 3) Launch the macro ==
# == if the cell has an alias, the length property will be something ==
# == like 'Spreadsheet.alias'. ==
# == if not, just something like 'Spreadsheet.C2' ==
# == You can select lines, points line, points, circle... ==
# == You can't select external objetcs (next version will be able to ==
# ========================================================================
# ========================================================================
__author__ = "2cv001"
__title__ = "Macro Sketch Constraint From Spreadsheet"
__date__ = "2023/03/16" #YYYY/MM/DD
__version__ = __date__
__icon__ = "https://wiki.freecad.org/File:SketchConstraintFromSpreadsheet.svg"
__Wiki__ = "https://wiki.freecad.org/Macro_sketchConstraintFromSpreadsheet"
import FreeCAD, FreeCADGui
from PySide import QtGui, QtCore
import PySide2
from PySide2.QtGui import QGuiApplication
import PySide
from PySide2 import QtWidgets
import Draft, Part, Sketcher
import itertools
import configparser
import os
from importlib import reload
importAliasOk = True
macroDirectory = FreeCAD.getUserMacroDir(True)
# try :
# import sketchConstraintFromSpreadsheetAlias
# reload(sketchConstraintFromSpreadsheetAlias)
# from sketchConstraintFromSpreadsheetAlias import setAlias
# except :
# importAliasOk=False
# App.Console.PrintWarning('You could Automatiquely add alias if you had'+
# 'sketchConstraintFromSpreadsheetAlias.py file in same directory'+
# 'than sketchConstraintFromSpreadsheetAlias') # On pr�vient l'utilisateur dans le rapport
#################################################################################
# part code for alias
#################################################################################
# parameters for alias creation
separateur = ' ' # typically put " " so blanks will be replaced by nouveauCaract
nouveauCaract = '' #Put for example "_" to have the separators replaced by "_". Put "" to have no separator
majuscule = True #set to True if you want "Diametre du cercle" to be "DiametreDuCercle"
changeTexteCellule = False # the text will only be changed if changeCellText is True.
# This does not change anything for the alias itself
premierCaractereEnMinuscule = True # Force the first character to be in lower case
# list of characters to be replaced by an equivalent. for example '�' will be replaced by 'e
# if you add characters, please send me a private message. Il will eventually add it in my code.
caracEquivalents = [ ['�','e'],['�','e'],['�','a'],['@','a'],['&','e'],['�','c'],['�','2'],["'",''] ]
def remplaceCartatParEquivalent(caractere):
# replaces a character with its equivalent if it exists
caracResult = caractere
for couple in caracEquivalents:
if (couple[0]==caractere):
caracResult=couple[1]
break
return caracResult
def remplaceCararcDansMot(mot):
#replaces all characters of the word with its equivalent if it exists
motResult = mot
for caract in mot:
a = remplaceCartatParEquivalent(caract)
motResult = motResult.replace(caract,a)
return motResult
def traitementChaineSource(chaineSource,separateur,nouveauCaract,majuscule):
# If separator is ' ' and nouveauCaract is '_', and majuscule is True
# transforms "Diametre du cylindre" into "Diametre_Du_Cylindre
chaineResult = ''
first = True
carctDeSeparation = ''
for mots in chaineSource.split(separateur):
mots = remplaceCararcDansMot(mots)
if (not (first)):
carctDeSeparation=nouveauCaract
if (majuscule):
chaineResult = chaineResult+nouveauCaract+mots[:1].upper()+mots[1:]
# We use "[:1]" instead of "[0]",
# for no crash in case of an empty string (which happens if the cell is empty)
else:
chaineResult=chaineResult+nouveauCaract+mots
if premierCaractereEnMinuscule :
chaineResult=chaineResult[:1].lower()+chaineResult[1:]
return chaineResult
def setAlias():
sels = Gui.Selection.getSelectionEx()
# If the user wants to trigger the creation of alias
# it is sufficient that he does not select any objects in the sketch
# but select cells containing strings in the left hand column
# of where he want the alias to be created
# so test if an object is selected :
# result : False if macro can continue.
if len(sels)!= 0 :
return False
else :
### A dialogue is displayed asking the user if he is sure
if QtGui.QMessageBox.warning(Gui.getMainWindow(),
'Warning','You did not select any object in an active sketch.'+
'\nDoes it mean you want to create alias automatiquely for cells at the right of selected cells ?',
QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel) == QtGui.QMessageBox.Cancel:
# if user dont want
return False
aw = Gui.getMainWindow().centralWidget().activeSubWindow() # Store the active window
#mySpreadSheet=trouveSheet(aw)
try :
mySpreadSheet = Gui.ActiveDocument.ActiveView.getSheet()
except :
QtWidgets.QMessageBox.warning(Gui.getMainWindow(), "Warning", "No active spreadsheet with selected cells.")
return True
# To get he complete list of all selected cells
sel_items = aw.widget().findChild(QtGui.QTableView).selectedIndexes()
### We define a function that will return the cell identifier from its row (r) and column (c) numbers
### Numbers start at 0 for the first row/column
### Columns are correctly managed with a 2-letter identifier
cellName = lambda r,c:'{}{}{}'.format(chr(c//26 + 64) if c//26 > 0 else '', chr(c%26+65), r+1)
# we check that one of the cells is not a numeric. If not, message and exit
for item in sel_items: # The selected cells are scanned
cell = cellName(item.row(),item.column()) # We retrieve the cell ID
activeCellContenu = mySpreadSheet.getContents(cell)
f = ''
try :
f = float(activeCellContenu)
except :
pass
if f != '' :
QtWidgets.QMessageBox.warning(Gui.getMainWindow(),
"Warning", "There is a number in one of the selected cells."+
" Alias is not possible. \nTry again after correction")
return False
for item in sel_items: # The selected cells are scanned
cell = cellName(item.row(),item.column()) # The cell identifier is retrieved
next_cell = cellName(item.row(), item.column()+1) # We get the ID of the cell next to the right
activeCellContenu = mySpreadSheet.getContents(cell)
# processing the character string contained in the cell
activeCellContenu = traitementChaineSource(activeCellContenu,separateur,nouveauCaract,majuscule)
if changeTexteCellule:# if the changeCellText parameter is set to True then we replace the text in the cell
mySpreadSheet.set(cell, activeCellContenu)
alias = activeCellContenu
try: # Bloc try to recover errors
mySpreadSheet.setAlias(next_cell, alias) # The alias is assigned to the right-hand neighbouring cell
except ValueError: # If a "ValueError" is triggered (which happens when the alias is not valid)
# The user is warned in the report
App.Console.PrintWarning("Can't set alias for cell {} : {} isn't valid\n".format(next_cell, alias))
return True
#################################################################################
# End part code for alias
#################################################################################
#=======================================================
# if no spreadsheet detected, ask for a creation
# return :
# 'exist' if one already exists
# 'new' if a new is create
# 'no' if user does not want it
#=======================================================
def isExistSpreadSheet() :
def firstSpreadSheet(doc = App.ActiveDocument):
for obj in doc.Objects:
if obj.TypeId == 'Spreadsheet::Sheet':
return obj
return None
mySpreadSheet=firstSpreadSheet()
if not mySpreadSheet :
wantNewSpreadSheet = QtWidgets.QMessageBox.question(None, "No spreadsheet detected",
"No spreadsheet in your document. Create one ?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if wantNewSpreadSheet == QtWidgets.QMessageBox.Yes:
mySpreadSheet = App.activeDocument().addObject('Spreadsheet::Sheet','Spreadsheet')
mySpreadSheet.ViewObject.doubleClicked()
return 'new'
else :
return 'no'
return 'exist'
#=======================================================
# Dialog box
# Ask user which sort of constraint is required
#=======================================================
class getConstraintType(QtGui.QDialog):
def __init__(self, widgetToFocus=None):
super(getConstraintType, self).__init__()
self.widgetToFocus = widgetToFocus
self.initUI()
def initUI(self):
self.setWindowIcon(QtGui.QIcon('dialog_icon.png'))
gridLayout = QtGui.QGridLayout()
option1Button = QtGui.QPushButton(QtGui.QIcon(":/icons/constraints/Constraint_HorizontalDistance.svg"), "")
option2Button = QtGui.QPushButton(QtGui.QIcon(":/icons/constraints/Constraint_VerticalDistance.svg"), "")
option3Button = QtGui.QPushButton(QtGui.QIcon(":/icons/constraints/Constraint_Length.svg"), "")
option1Button.setText("Lenght constrainte X")
option2Button.setText("Lenght constrainte Y")
option3Button.setText("Lenght constrainte")
option1Button.setToolTip("Lenght constrainte X")
option2Button.setToolTip("Lenght constrainte Y")
option3Button.setToolTip("Lenght constrainte")
option1Button.clicked.connect(self.onOption1)
option2Button.clicked.connect(self.onOption2)
option3Button.clicked.connect(self.onOption3)
option4Button = QtGui.QPushButton(QtGui.QIcon(":/icons/application-exit.svg"), "Cancel")
option4Button.setToolTip("Option 4 tooltip")
option4Button.clicked.connect(self.onOption4)
gridLayout.addWidget(option1Button, 0, 0)
gridLayout.addWidget(option2Button, 0, 1)
gridLayout.addWidget(option3Button, 1, 0)
gridLayout.addWidget(option4Button, 1, 1)
self.setLayout(gridLayout)
self.setGeometry(250, 250, 0, 50)
self.setWindowTitle("Choose a constraint type")
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
self.choiceConstraint = ''
option1Button.setFocusPolicy(QtCore.Qt.NoFocus)
option2Button.setFocusPolicy(QtCore.Qt.NoFocus)
option3Button.setFocusPolicy(QtCore.Qt.NoFocus)
option4Button.setFocusPolicy(QtCore.Qt.NoFocus)
# set focus to specified widget
if self.widgetToFocus == 'DistanceX':
option1Button.setFocus()
elif self.widgetToFocus == 'DistanceY':
option2Button.setFocus()
elif self.widgetToFocus == 'Distance':
option3Button.setFocus()
# Add checkbox
self.checkBox = QtGui.QCheckBox("Conflict detection")
self.checkBox.setChecked(True)
gridLayout.addWidget(self.checkBox, 2, 0, 1, 2)
self.checkBox.clicked.connect(self.onOptionCheckBox)
# read ini file to get last checkBoxState
config = configparser.ConfigParser()
try :
config.read(os.path.join(macroDirectory, 'constraintFromTabIcone.ini'))
# read ini file to know last time state
lasChecked = config.getboolean('DEFAULT', 'save_checkbox_state')
self.checkBox.setChecked(lasChecked)
except :
print('no ini file. Ini file will be create for next launch')
# window positioning
centerPoint = QGuiApplication.screens()[0].geometry().center()
self.move(centerPoint - self.frameGeometry().center())
def onOption1(self):
self.choiceConstraint = 'DistanceX'
self.close()
def onOption2(self):
self.choiceConstraint = 'DistanceY'
self.close()
def onOption3(self):
self.choiceConstraint = 'Distance'
self.close()
def onOption4(self):
self.choiceConstraint = 'Cancel'
self.close()
def onOptionCheckBox(self):
# Save checkbox state to file
filePath = os.path.join(macroDirectory, 'constraintFromTabIcone.ini')
config = configparser.ConfigParser()
config['DEFAULT'] = {'save_checkbox_state': str(int(self.getCheckBoxState()))}
with open(filePath, 'w') as configfile:
config.write(configfile)
def getCheckBoxState(self):
return self.checkBox.isChecked()
#=======================================================
# Give the focus to editing sketch window
# no parameter
# use : activateSketchEditingWindow()
#=======================================================
def activateSketchEditingWindow():
def searchForNode(tree, childName, maxLevel=0):
return recursiveSearchForNode(tree, childName, maxLevel, 1)
def recursiveSearchForNode(tree, childName, maxLevel, currentLevel):
try:
if tree.getByName(childName):
return True;
elif maxLevel > 0 and currentLevel >= maxLevel:
return False;
else:
for child in tree.getChildren():
if recursiveSearchForNode(child, childName, maxLevel, currentLevel+1):
return True
except:
pass
return False
doc = Gui.ActiveDocument
if not doc:
QtWidgets.QMessageBox.information(Gui.getMainWindow(), "Activate window", "No active document")
return
views = Gui.ActiveDocument.mdiViewsOfType("Gui::View3DInventor")
if not views:
QtWidgets.QMessageBox.information(Gui.getMainWindow(), "Activate window", "No 3D view opened for active document")
return
editView=None
for view in views:
if searchForNode(view.getSceneGraph(), "Sketch_EditRoot",3):
editView = view
break
if not editView:
QtWidgets.QMessageBox.information(Gui.getMainWindow(), "Activate window", "No 3D view has sketch in edit mode for active document")
return
for win in Gui.getMainWindow().centralWidget().subWindowList():
if editView.graphicsView() in win.findChildren(QtWidgets.QGraphicsView):
Gui.getMainWindow().centralWidget().setActiveSubWindow(win)
break
######################################################################################
# to get necessary values for the constraint
# Parameters :
# sel : selection of objects (a line, 2 points..).
# numOrdreObjSelected if we want the first objetc selected or the second.
# indexExtremite if we want the start point (1) or the end point (2), if exist, of the sel
# return features of a point
# - typeInSubElementName
# - indexObjectHavingPoint index of the object having the point (line...)
# - indexExtremiteLine index of the ends (points) of the line (=start point or end point)
# -x,y : coordinates of the point
#####################################################################################
def featuresObjSelected (mySketch,sel,numOrdreObjSelected,indexExtremite) :
indexExtremiteLine = 1
indexObjectHavingPoint = -10
typeIdGeometry = None
x,y = 0,0
itemName=sel.SubElementNames[numOrdreObjSelected] # ex Edge5 ( line)
if itemName == 'RootPoint' :
typeInSubElementName = 'RootPoint'
indexObjectHavingPoint = -1
typeIdGeometry = mySketch.Geometry[indexObjectHavingPoint].TypeId
x,y = 0,0
else :
typeInSubElementName, numInNameStr = [''.join(c) for _, c in itertools.groupby(itemName, str.isalpha)] # Edge5 renvoie'Edge' et '5'
numInName=int(numInNameStr) # index de ce qui a �t� s�lectionn� =numInName1-1 car commence � 0
# only one selected object
if typeInSubElementName == 'Edge'and len(sel.SubElementNames)==1: # selection is only one line
indexObjectHavingPoint = numInName-1
indexExtremiteLine = indexExtremite
typeIdGeometry = mySketch.Geometry[indexObjectHavingPoint].TypeId
# typeIdGeometry : 'Part::GeomCircle' or 'Part::GeomLineSegment'
if typeIdGeometry in ['Part::GeomLineSegment'] :
if indexExtremite == 1 :
x = mySketch.Geometry[indexObjectHavingPoint].StartPoint.x
y = mySketch.Geometry[indexObjectHavingPoint].StartPoint.y
elif indexExtremite == 2 :
x = mySketch.Geometry[indexObjectHavingPoint].EndPoint.x
y = mySketch.Geometry[indexObjectHavingPoint].EndPoint.y
# We selected a circle but for center (two objects selected)
if typeInSubElementName == 'Edge'and len(sel.SubElementNames)==2:
indexObjectHavingPoint = numInName-1
typeIdGeometry = mySketch.Geometry[indexObjectHavingPoint].TypeId
print('425 typeIdGeometry',typeIdGeometry)
if typeIdGeometry in ['Part::GeomCircle','Part::GeomArcOfCircle'] :
x=mySketch.Geometry[indexObjectHavingPoint].Location.x
y=mySketch.Geometry[indexObjectHavingPoint].Location.y
indexExtremiteLine=3 # 3 for center
# We selected a vertex
if typeInSubElementName == 'Vertex' : # selection is 2 points. sel is a vertex (a point of a line) :
indexObjectHavingPoint, indexExtremiteLine = sel.Object.getGeoVertexIndex(numInName-1)
typeIdGeometry = mySketch.Geometry[indexObjectHavingPoint].TypeId
if mySketch.Geometry[indexObjectHavingPoint].TypeId=='Part::GeomLineSegment' :
if indexExtremiteLine == 1 :
x=mySketch.Geometry[indexObjectHavingPoint].StartPoint.x
y=mySketch.Geometry[indexObjectHavingPoint].StartPoint.y
if indexExtremiteLine == 2:
x = mySketch.Geometry[indexObjectHavingPoint].EndPoint.x
y = mySketch.Geometry[indexObjectHavingPoint].EndPoint.y
if mySketch.Geometry[indexObjectHavingPoint].TypeId=='Part::GeomPoint' :
x = mySketch.Geometry[indexObjectHavingPoint].X
y = mySketch.Geometry[indexObjectHavingPoint].Y
# we select a vertex Circle (so the center)
if mySketch.Geometry[indexObjectHavingPoint].TypeId in ['Part::GeomCircle','Part::GeomArcOfCircle'] :
x = mySketch.Geometry[indexObjectHavingPoint].Location.x
y = mySketch.Geometry[indexObjectHavingPoint].Location.y
if typeInSubElementName == 'Constraint' and len(sel.SubElementNames) == 1 :
indexConstraint = numInName-1
indexObjectHavingPoint = indexConstraint
typeIdGeometry = 'Constraint'
return typeIdGeometry,typeInSubElementName, indexObjectHavingPoint, indexExtremiteLine, x ,y
##########################################
# call at end
##########################################
def procEnd():
activateSketchEditingWindow()
##########################################
# function returning selected objects at GUI level
# =Sketch, SpreadSheet ....
# parameter :
# '' = no filter
# 'Spreadsheet::Sheet' for spreadsheets only
# 'Sketcher::SketchObject' for sketches etc...
# output: an array of sketch objects, spreadsheets etc.
##########################################
def getGuiObjsSelect(type = ''):
tabGObjSelect=[]
selections = Gui.Selection.getCompleteSelection()
for sel in (selections):
if hasattr(sel, 'Object'): # depend freecad version
if type == '' or sel.Object.TypeId == type :
tabGObjSelect.append(sel.Object)
else :
obj = App.ActiveDocument.getObject(sel.Name)
if type == '' or obj.TypeId == type :
tabGObjSelect.append(obj)
return tabGObjSelect
##########################################
# Main proceddure
##########################################
def main():
#initialization
sheckBoxConstraintConflicState = False
indexConstraint = -1
if isExistSpreadSheet() in ['no','new'] :
return
# have a look if user wants alias
if importAliasOk :
if setAlias():
return
try :
mySketch = ActiveSketch
except :
QtWidgets.QMessageBox.information(None,"Warning","Select object must be done in edition mode")
activateSketchEditingWindow()
return
mySketchName = mySketch.Name #actually not use
# Part SpreadSheet
#---------------------------------
sheets = getGuiObjsSelect('Spreadsheet::Sheet')
for sheet in sheets :
Gui.Selection.removeSelection(FreeCAD.ActiveDocument.Name,sheet.Name)
try :
mySpreadSheet = Gui.ActiveDocument.ActiveView.getSheet()
except :
QtWidgets.QMessageBox.information(None,"Warning",
"1- Select a line or 2 points"+
"\n 2- go to a spreadsheet"+
"\n 3- select the cell containing the value."+
"\n 4- stay in the spreadsheet and launch the macro")
activateSketchEditingWindow()
return
mySpreadSheetName = mySpreadSheet.Name
# select the Spreadsheet To be able to retrieve the selected cell :
mySpreadSheet.ViewObject.doubleClicked()
# retrieve the selected cell
ci = lambda :Gui.getMainWindow().centralWidget().activeSubWindow().widget().findChild(QtGui.QTableView).currentIndex()
cellCode = '{}{}{}'.format(chr(ci().column()//28 + 64) if ci().column()//26 > 0 else '', chr(ci().column()%26+65), ci().row()+1)
try:
cellContents = float(mySpreadSheet.get(cellCode))
except:
QtWidgets.QMessageBox.information(None,"Warning",
"Click on a cell with a numeric value before runing the macro")
return
cellAlias= App.ActiveDocument.getObject(mySpreadSheetName).getAlias(cellCode)
# Part sketch
#----------------------------
sels = Gui.Selection.getSelectionEx()
if len(sels) == 0 or len(sels[0].SubElementNames) == 0 :
QtWidgets.QMessageBox.information(None,"Warning","Anathing is select.\n"+
"Select 1 line, 2 points or a constraint in a sketch before selecting a cell in the spreadsheet")
activateSketchEditingWindow()
return
elif len(sels[0].SubElementNames) > 2 :
QtWidgets.QMessageBox.information(None,"Warning","Too many objects selected.\n"+
"Select 1 line, 2 points or a constraint in a sketch before selecting a cell in the spreadsheet")
activateSketchEditingWindow()
return
else :
# only one obj selected
#------------------------
if len(sels[0].SubElementNames)==1 : # only one obj selected
#startPoint of the line
(typeIdGeometry1,typeInSubElementName1, indexObjectHavingPoint1, indexExtremiteLine1, x1 ,y1)=featuresObjSelected (ActiveSketch, sels[0],0,1)
if typeInSubElementName1=='Constraint' and len(sels[0].SubElementNames)==1 :
indexConstraint=indexObjectHavingPoint1
elif typeIdGeometry1=='Part::GeomLineSegment' :
(typeIdGeometry2, typeInSubElementName2, indexObjectHavingPoint2, indexExtremiteLine2, x2 ,y2)=featuresObjSelected (ActiveSketch, sels[0],0,2)
# two obj selected
#------------------------
if len(sels[0].SubElementNames)== 2: # two obj selected
(typeIdGeometry1,typeInSubElementName1, indexObjectHavingPoint1, indexExtremiteLine1, x1 ,y1)=featuresObjSelected (ActiveSketch, sels[0],0,1)
print('575 typeIdGeometry1',typeIdGeometry1)
(typeIdGeometry2,typeInSubElementName2, indexObjectHavingPoint2, indexExtremiteLine2, x2 ,y2)=featuresObjSelected (ActiveSketch, sels[0],1,1)
if ((typeInSubElementName1 not in ('Vertex' , 'RootPoint') or typeInSubElementName2 not in ('Vertex','RootPoint'))
and not(typeIdGeometry1 in ('Part::GeomCircle','Part::GeomArcOfCircle') and typeInSubElementName1 in ['Edge'])) :
QtWidgets.QMessageBox.information(None,"Warning","2 objects are selected but not 2 points .\n"+
"Select 1 line, 2 points or a constraint in a sketch before selecting a cell in the spreadsheet")
activateSketchEditingWindow()
return
#--------------------------------------
# line or points have been selected have a look if we need to swap points
# -------------------------------------
if ((len(sels[0].SubElementNames) == 1 and typeIdGeometry1 in['Part::GeomLineSegment'] )
or (len(sels[0].SubElementNames) == 2 and typeIdGeometry1
in['Part::GeomLineSegment','Part::GeomCircle', 'Part::GeomArcOfCircle', 'Part::GeomPoint'] )) :
# ask the user what kind of constraint he wants
#------------------------------------
# to give focus on the good button
# (Button DistanceX if the two points are more horizontal than vertical)
if abs(x1 - x2) > abs(y1 - y2) :
buttonHavingFocus = 'DistanceX'
else :
buttonHavingFocus = 'DistanceY'
form = getConstraintType(buttonHavingFocus)
form.exec_()
# is the checkboxSheced ?
sheckBoxConstraintConflicState = form.getCheckBoxState()
if form.choiceConstraint in ('Cancel','') :
activateSketchEditingWindow()
return
myConstraint = form.choiceConstraint # 'DistanceX' or 'DistanceY' or 'Distance'
if (myConstraint == 'DistanceX' and x1 > x2) or (myConstraint == 'DistanceY' and y1 > y2) :
indexObjectHavingPoint1,indexObjectHavingPoint2 = indexObjectHavingPoint2,indexObjectHavingPoint1
indexExtremiteLine1,indexExtremiteLine2 = indexExtremiteLine2,indexExtremiteLine1
# create constraint
#=================================
if cellAlias == None :
cellExpression = mySpreadSheetName + '.' + cellCode
else :
cellExpression = mySpreadSheetName + '.' + cellAlias
if (len(sels[0].SubElementNames) == 1 and typeIdGeometry1 in ['Part::GeomCircle','Part::GeomArcOfCircle'] ) :
indexConstraint = mySketch.addConstraint(Sketcher.Constraint('Diameter', indexObjectHavingPoint1, cellContents))
elif typeIdGeometry1 != 'Constraint' : # no selected constraint, just line or points
#create the constraint
indexConstraint = mySketch.addConstraint(Sketcher.Constraint(myConstraint
, indexObjectHavingPoint1,indexExtremiteLine1,indexObjectHavingPoint2,indexExtremiteLine2, cellContents))
# for all type, set the constraint'formula' (ex : 'spreadSheet.unAlias'
mySketch.setExpression('Constraints[' + str(indexConstraint) + ']',cellExpression)
# put Sketch window ahead
activateSketchEditingWindow()
FreeCADGui.Selection.clearSelection()
# FreeCAD.ActiveDocument.recompute()
ActiveSketch.touch()
ActiveSketch.recompute()
#if Gui.ActiveDocument.getInEdit() == Gui.ActiveDocument.Sketch:
#Gui.ActiveDocument.Sketch.doubleClicked()
# is ther constraintes conflicts ?
if sheckBoxConstraintConflicState :
#if App.activeDocument().isTouched(): # isTouched is not ok in Daily Freecad
if 'Invalid' in mySketch.State :
a = QtWidgets.QMessageBox.question(None, "",
"Constraints conflic detected. Cancel constraint ? ",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if a == QtWidgets.QMessageBox.Yes:
mySketch.delConstraint(indexConstraint)
FreeCAD.ActiveDocument.recompute()
activateSketchEditingWindow()
return
if __name__ == '__main__':
main()
#procEnd()
IMHO there are still improvements to stop the ini file not found repeatedly appearing in the Report View but it does what it says on the tin.
Tested using:
Code: Select all
OS: Windows 7 Version 6.1 (Build 7601: SP 1)
Word size of FreeCAD: 64-bit
Version: 0.21.0.32258 (Git)
Build type: Release
Branch: master
Hash: 0f4acc55beb66dcc9dfab442344c6286d6a2913e
Python 3.8.10, Qt 5.15.2, Coin 4.0.1, Vtk 8.2.0, OCC 7.6.3
Locale: English/United Kingdom (en_GB)
Installed mods:
* A2plus 0.4.60f
* Alternate_OpenSCAD 1.0.0
* Assembly3 0.11.4
* CfdOF 1.20.4
* Curves 0.6.6
* fasteners 0.4.54
* fcgear 1.0.0
* freecad.xray 2022.4.17
* Help 1.0.3
* Manipulator 1.5.0
* Plot 2022.4.17
* Render 2023.2.2
* sheetmetal 0.2.59
* Silk 1.0.0
* timber
Re: New Macro " Sketch Constraint From Spreadsheet"
Thank for your feedback !
I did not know that the
created problems with some configurations. I will delete this comments. I found some comments in French. I will delete or translate them.
I saw that you suggest to use
rather than
It looks more simple. I don't know if this always gives the same result for all configurations. I guess it does.
I saw that you have problems with some characters not used in English
With my configuration, it is ok with :
I don't know what to do about that
I did not see any other differences. Did I miss anything else?
I did not know that the
Code: Select all
'''
I saw that you suggest to use
Code: Select all
FreeCAD.getUserMacroDir(True)
Code: Select all
FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro").GetString("MacroPath") + "\\"
I saw that you have problems with some characters not used in English
With my configuration, it is ok with :
I don't know what to do about that
I did not see any other differences. Did I miss anything else?
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
Re: New Macro " Sketch Constraint From Spreadsheet"
@Syres Thanks again !!
Is it ok like this ?
Is it ok like this ?
Code: Select all
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# =========================================================================
# Macro Sketch Constraint From Spreadsheet
# file name : sketchConstraintFromSpreadsheet.FCMacro
# ========================================================================
# == ==
# == Adds a length constraint to a line or between 2 points ==
# == using a spreadsheet cell alias or name (ex. C2). ==
# == Future changes to the spreadsheet will update the constraint. ==
# == if necessary, the macro help you to create alias ==
# == USE: ==
# == 1) Select 1 line, 2 points or a constraint ==
# == 2) Click on a spreadsheet cell ==
# == 3) Launch the macro ==
# == if the cell has an alias, the length property will be something ==
# == like 'Spreadsheet.alias'. ==
# == if not, just something like 'Spreadsheet.C2' ==
# == You can select lines, points line, points, circle... ==
# == You can't select external objetcs (next version will be able to ==
# ========================================================================
# ========================================================================
__author__ = "2cv001"
__title__ = "Macro Sketch Constraint From Spreadsheet"
__date__ = "2023/03/22" #YYYY/MM/DD
__version__ = __date__
__icon__ = "https://wiki.freecad.org/File:SketchConstraintFromSpreadsheet.svg"
__Wiki__ = "https://wiki.freecad.org/Macro_sketchConstraintFromSpreadsheet"
import FreeCAD, FreeCADGui
from PySide import QtGui, QtCore
import PySide2
from PySide2.QtGui import QGuiApplication
import PySide
from PySide2 import QtWidgets
import Draft, Part, Sketcher
import itertools
import configparser
import os
from importlib import reload
importAliasOk = True
#################################################################################
# part code for alias
#################################################################################
# parameters for alias creation
separateur = " " # typically put " " so blanks will be replaced by nouveauCaract
nouveauCaract = '' #Put for example "_" to have the separators replaced by "_". Put "" to have no separator
majuscule = True #set to True if you want "Diametre du cercle" to be "DiametreDuCercle"
changeTexteCellule = False # the text will only be changed if changeCellText is True.
# This does not change anything for the allias itself
premierCaractereEnMinuscule = True # Force the first character to be in lower case
# list of characters to be replaced by an equivalent. for example 'é' will be replaced by 'e
# if you add characters, please send me a private message. Il will eventually add it in my code.
caracEquivalents = [ ['é','e'],['è','e'],['à','a'],['@','a'],['&','e'],['ç','c'],
['²','2'],["'",''],['?',''],['"',''],['(',''],[')',''],['#',''],
['$',''],['+',''],['-',''],['*',''],['/',''],['\\',''] ]
def remplaceCartatParEquivalent(caractere):
# replaces a character with its equivalent if it exists
caracResult = caractere
for couple in caracEquivalents:
if (couple[0] == caractere):
caracResult = couple[1]
break
return caracResult
def remplaceCararcDansMot(mot):
#replaces all characters of the word with its equivalent if it exists
motResult = mot
for caract in mot:
a = remplaceCartatParEquivalent(caract)
motResult = motResult.replace(caract, a)
return motResult
def traitementChaineSource(chaineSource, separateur, nouveauCaract, majuscule):
# If separator is ' ' and nouveauCaract is '_', and majuscule is True
# transforms "Diametre du cylindre" into "Diametre_Du_Cylindre
chaineResult = ''
first = True
carctDeSeparation = ''
for mots in chaineSource.split(separateur):
mots = remplaceCararcDansMot(mots)
if (not (first)):
carctDeSeparation = nouveauCaract
if (majuscule):
chaineResult = chaineResult + nouveauCaract + mots[:1].upper() + mots[1:]
# We use "[:1]" instead of "[0]",
# for no crash in case of an empty string (which happens if the cell is empty)
else:
chaineResult = chaineResult + nouveauCaract + mots
if premierCaractereEnMinuscule :
chaineResult = chaineResult[:1].lower() + chaineResult[1:]
return chaineResult
def setAlias():
sels = Gui.Selection.getSelectionEx()
# If the user wants to trigger the creation of alias
# it is sufficient that he does not select any objects in the sketch
# but select cells containing strings in the left hand column
# of where he want the alias to be created
# so test if an object is selected :
# result : False if macro can continue.
if len(sels) !=0 :
return False
else :
### A dialogue is displayed asking the user if he is sure
if QtGui.QMessageBox.warning(Gui.getMainWindow(),
'Warning', 'You did not select any object in an active sketch.' +
'\nDoes it mean you want to create alias automatiquely for cells at the right of selected cells ?',
QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel) == QtGui.QMessageBox.Cancel:
# if user dont want
return False
aw = Gui.getMainWindow().centralWidget().activeSubWindow() # Store the active window
try :
mySpreadSheet = Gui.ActiveDocument.ActiveView.getSheet()
except :
QtWidgets.QMessageBox.warning(Gui.getMainWindow(), "Warning", "No active spreadsheet with selected cells.")
return True
# To get list of all selected cells
sel_items = aw.widget().findChild(QtGui.QTableView).selectedIndexes()
### We define a function that will return the cell identifier from its row (r) and column (c) numbers
### Numbers start at 0 for the first row/column
### Columns are correctly managed with a 2-letter identifier
cellName = lambda r,c:'{}{}{}'.format(chr(c//26 + 64) if c//26 > 0 else '', chr(c%26 + 65), r + 1)
# we check that one of the cells is not a numeric. If not, message and exit
for item in sel_items: # The selected cells are scanned
cell = cellName(item.row(), item.column()) # We retrieve the cell ID
activeCellContenu = mySpreadSheet.getContents(cell)
f = ''
try :
f = float(activeCellContenu)
except :
pass
if f!= '' :
QtWidgets.QMessageBox.warning(Gui.getMainWindow(),
"Warning", "There is a number in one of the selected cells." +
" Alias is not possible. \nTry again after correction")
return False
for item in sel_items: # The selected cells are scanned
cell = cellName(item.row(), item.column()) # The cell identifier is retrieved
next_cell = cellName(item.row(), item.column() + 1) # We get the ID of the cell next to the right
activeCellContenu = mySpreadSheet.getContents(cell)
# processing the character string contained in the cell
activeCellContenu = traitementChaineSource(activeCellContenu, separateur, nouveauCaract, majuscule)
if changeTexteCellule:# if the changeCellText parameter is set to True then we replace the text in the cell
mySpreadSheet.set(cell, activeCellContenu)
alias = activeCellContenu
try: # Bloc try to recover errors
mySpreadSheet.setAlias(next_cell, alias) # The alias is assigned to the right-hand neighbouring cell
except ValueError: # If a "ValueError" is triggered (which happens when the alias is not valid)
# The user is warned in the report
App.Console.PrintWarning("Can't set alias for cell {} : {} isn't valid\n".format(next_cell, alias))
return True
#################################################################################
# End part code for alias
#################################################################################
#=======================================================
# if no spreadsheet detected, ask for a creation
# return :
# 'exist' if one already exists
# 'new' if a new is create
# 'no' if user does not want it
#=======================================================
def isExistSpreadSheet() :
def firstSpreadSheet(doc = App.ActiveDocument):
for obj in doc.Objects:
if obj.TypeId == 'Spreadsheet::Sheet':
return obj
return None
mySpreadSheet = firstSpreadSheet()
if not mySpreadSheet :
wantNewSpreadSheet = QtWidgets.QMessageBox.question(None, "No spreadsheet detected",
"No spreadsheet in your document. Create one ?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if wantNewSpreadSheet == QtWidgets.QMessageBox.Yes:
mySpreadSheet = App.activeDocument().addObject('Spreadsheet::Sheet', 'Spreadsheet')
mySpreadSheet.ViewObject.doubleClicked()
return 'new'
else :
return 'no'
return 'exist'
#=======================================================
# Dialog box
# Ask user which sort of constraint is required
#=======================================================
class getConstraintType(QtGui.QDialog):
def __init__(self, widgetToFocus = None):
super(getConstraintType, self).__init__()
self.widgetToFocus = widgetToFocus
self.initUI()
def initUI(self):
self.setWindowIcon(QtGui.QIcon('dialog_icon.png'))
gridLayout = QtGui.QGridLayout()
option1Button = QtGui.QPushButton(QtGui.QIcon(":/icons/constraints/Constraint_HorizontalDistance.svg"), "")
option2Button = QtGui.QPushButton(QtGui.QIcon(":/icons/constraints/Constraint_VerticalDistance.svg"), "")
option3Button = QtGui.QPushButton(QtGui.QIcon(":/icons/constraints/Constraint_Length.svg"), "")
option1Button.setText("Lenght constrainte X")
option2Button.setText("Lenght constrainte Y")
option3Button.setText("Lenght constrainte")
option1Button.setToolTip("Lenght constrainte X")
option2Button.setToolTip("Lenght constrainte Y")
option3Button.setToolTip("Lenght constrainte")
option1Button.clicked.connect(self.onOption1)
option2Button.clicked.connect(self.onOption2)
option3Button.clicked.connect(self.onOption3)
option4Button = QtGui.QPushButton(QtGui.QIcon(":/icons/application-exit.svg"), "Cancel")
option4Button.setToolTip("Option 4 tooltip")
option4Button.clicked.connect(self.onOption4)
gridLayout.addWidget(option1Button, 0, 0)
gridLayout.addWidget(option2Button, 0, 1)
gridLayout.addWidget(option3Button, 1, 0)
gridLayout.addWidget(option4Button, 1, 1)
self.setLayout(gridLayout)
self.setGeometry(250, 250, 0, 50)
self.setWindowTitle("Choose a constraint type")
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
self.choiceConstraint = ''
option1Button.setFocusPolicy(QtCore.Qt.NoFocus)
option2Button.setFocusPolicy(QtCore.Qt.NoFocus)
option3Button.setFocusPolicy(QtCore.Qt.NoFocus)
option4Button.setFocusPolicy(QtCore.Qt.NoFocus)
# set focus to specified widget
if self.widgetToFocus == 'DistanceX':
option1Button.setFocus()
elif self.widgetToFocus == 'DistanceY':
option2Button.setFocus()
elif self.widgetToFocus == 'Distance':
option3Button.setFocus()
# Add checkbox
self.checkBox = QtGui.QCheckBox("Conflict detection")
self.checkBox.setChecked(True)
gridLayout.addWidget(self.checkBox, 2, 0, 1, 2)
self.checkBox.clicked.connect(self.onOptionCheckBox)
# read ini file to get last checkBoxState
config = configparser.ConfigParser()
# macroDirectory = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro").GetString("MacroPath") + "\\"
macroDirectory = FreeCAD.getUserMacroDir(True)
try :
filePath = os.path.join(macroDirectory, "constraintFromTabIcone.ini")
config.read(filePath)
# read ini file to know last time state
lasChecked = config.getboolean('DEFAULT', 'save_checkbox_state')
self.checkBox.setChecked(lasChecked)
except :
# print('no ini file. Ini file will be create for next launch')
pass
# window positioning
centerPoint = QGuiApplication.screens()[0].geometry().center()
self.move(centerPoint - self.frameGeometry().center())
def onOption1(self):
self.choiceConstraint = 'DistanceX'
self.close()
def onOption2(self):
self.choiceConstraint = 'DistanceY'
self.close()
def onOption3(self):
self.choiceConstraint = 'Distance'
self.close()
def onOption4(self):
self.choiceConstraint = 'Cancel'
self.close()
def onOptionCheckBox(self):
#macroDirectory = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro").GetString("MacroPath") + "\\"
macroDirectory = FreeCAD.getUserMacroDir(True)
# Save checkbox state to file
filePath = os.path.join(macroDirectory, "constraintFromTabIcone.ini")
config = configparser.ConfigParser()
config['DEFAULT'] = {'save_checkbox_state': str(int(self.getCheckBoxState()))}
with open(filePath, 'w') as configfile:
config.write(configfile)
def getCheckBoxState(self):
return self.checkBox.isChecked()
#=======================================================
# Give the focus to editing sketch window
# no parameter
# use : activateSketchEditingWindow()
#=======================================================
def activateSketchEditingWindow():
def searchForNode(tree, childName, maxLevel = 0):
return recursiveSearchForNode(tree, childName, maxLevel, 1)
def recursiveSearchForNode(tree, childName, maxLevel, currentLevel):
try:
if tree.getByName(childName):
return True;
elif maxLevel > 0 and currentLevel >= maxLevel:
return False;
else:
for child in tree.getChildren():
if recursiveSearchForNode(child, childName, maxLevel, currentLevel + 1):
return True
except:
pass
return False
doc = Gui.ActiveDocument
if not doc:
QtWidgets.QMessageBox.information(Gui.getMainWindow(), "Activate window", "No active document")
return
views = Gui.ActiveDocument.mdiViewsOfType("Gui::View3DInventor")
if not views:
QtWidgets.QMessageBox.information(Gui.getMainWindow(), "Activate window", "No 3D view opened for active document")
return
editView = None
for view in views:
if searchForNode(view.getSceneGraph(), "Sketch_EditRoot", 3):
editView = view
break
if not editView:
QtWidgets.QMessageBox.information(Gui.getMainWindow(), "Activate window", "No 3D view has sketch in edit mode for active document")
return
for win in Gui.getMainWindow().centralWidget().subWindowList():
if editView.graphicsView() in win.findChildren(QtWidgets.QGraphicsView):
Gui.getMainWindow().centralWidget().setActiveSubWindow(win)
break
######################################################################################
# to get necessary values for the constraint
# Parameters :
# sel : selection of objects (a line, 2 points..).
# numOrdreObjSelected if we want the first objetc selected or the second.
# indexExtremite if we want the start point (1) or the end point (2), if exist, of the sel
# return features of a point
# - typeInSubElementName
# - indexObjectHavingPoint index of the object having the point (line...)
# - indexExtremiteLine index of the ends (points) of the line (= start point or end point)
# -x, y : coordinates of the point
#####################################################################################
def featuresObjSelected (mySketch, sel, numOrdreObjSelected, indexExtremite) :
indexExtremiteLine = 1
indexObjectHavingPoint = -10
typeIdGeometry = None
x, y = 0, 0
itemName = sel.SubElementNames[numOrdreObjSelected] # ex Edge5 ( line)
if itemName == 'RootPoint' :
typeInSubElementName = 'RootPoint'
indexObjectHavingPoint = -1
typeIdGeometry = mySketch.Geometry[indexObjectHavingPoint].TypeId
x, y = 0, 0
else :
typeInSubElementName, numInNameStr = [''.join(c) for _, c in itertools.groupby(itemName, str.isalpha)] # Edge5 renvoie'Edge' et '5'
numInName = int(numInNameStr)
# only one selected object
if typeInSubElementName == 'Edge'and len(sel.SubElementNames) == 1: # selection is only one line
indexObjectHavingPoint = numInName - 1
indexExtremiteLine = indexExtremite
typeIdGeometry = mySketch.Geometry[indexObjectHavingPoint].TypeId
# typeIdGeometry : 'Part::GeomCircle' or 'Part::GeomLineSegment'
if typeIdGeometry in ['Part::GeomLineSegment'] :
if indexExtremite == 1 :
x = mySketch.Geometry[indexObjectHavingPoint].StartPoint.x
y = mySketch.Geometry[indexObjectHavingPoint].StartPoint.y
elif indexExtremite == 2 :
x = mySketch.Geometry[indexObjectHavingPoint].EndPoint.x
y = mySketch.Geometry[indexObjectHavingPoint].EndPoint.y
if typeInSubElementName == 'ExternalEdge'and len(sel.SubElementNames) == 1:
print(f"The selected ExternalEdge is available for constraints as geometry ({-numInName - 2})")
indexObjectHavingPoint = - numInName - 2
indexExtremiteLine = indexExtremite
tabExternalGeomery = []
for item in ActiveSketch.ExternalGeometry :
for objName in item [1] :
# to get something like
# [('Sketch', 'Edge2'), ('Sketch001', 'Edge2'), ('Sketch001', 'Edge1'), ('Sketch', 'Edge1')]
tabExternalGeomery.append((item[0].Name, objName))
externalSketchName, externalGeometryName = tabExternalGeomery[numInName - 1]
iTypeExternal, iNumStr = [''.join(c) for _, c in itertools.groupby(externalGeometryName, str.isalpha)]
print('iTypeExternal', iTypeExternal)
iNumExtGeometry = int(iNumStr)
print(f"Its properties are reachable at App.ActiveDocument.{externalSketchName}.Geometry[{iNumExtGeometry - 1}]")
typeIdGeometry = App.ActiveDocument.getObject(externalSketchName).Geometry[iNumExtGeometry - 1].TypeId
if typeIdGeometry == 'Part::GeomLineSegment' :
print('Properties : ', App.ActiveDocument.getObject(externalSketchName).Geometry[iNumExtGeometry - 1])
print('First point vector', App.ActiveDocument.getObject(externalSketchName).Geometry[iNumExtGeometry - 1].StartPoint)
print('End point vector', App.ActiveDocument.getObject(externalSketchName).Geometry[iNumExtGeometry - 1].EndPoint)
if indexExtremite == 1 :
x = App.ActiveDocument.getObject(externalSketchName).Geometry[iNumExtGeometry - 1].StartPoint.x
y = App.ActiveDocument.getObject(externalSketchName).Geometry[iNumExtGeometry - 1].StartPoint.y
elif indexExtremite == 2 :
x = App.ActiveDocument.getObject(externalSketchName).Geometry[iNumExtGeometry - 1].EndPoint.x
y = App.ActiveDocument.getObject(externalSketchName).Geometry[iNumExtGeometry - 1].EndPoint.y
if typeIdGeometry == 'Part::GeomCircle' :
# to do
print('Its a circle. Center vector :', App.ActiveDocument.getObject(externalSketchName).Geometry[iNumExtGeometry - 1].Center)
# We selected a circle but for center (two objects selected)
if typeInSubElementName == 'Edge'and len(sel.SubElementNames) == 2:
indexObjectHavingPoint = numInName - 1
typeIdGeometry = mySketch.Geometry[indexObjectHavingPoint].TypeId
if typeIdGeometry in ['Part::GeomCircle', 'Part::GeomArcOfCircle'] :
x = mySketch.Geometry[indexObjectHavingPoint].Location.x
y = mySketch.Geometry[indexObjectHavingPoint].Location.y
indexExtremiteLine = 3 # 3 for center
# We selected a vertex
if typeInSubElementName == 'Vertex' : # selection is 2 points. sel is a vertex (a point of a line) :
indexObjectHavingPoint, indexExtremiteLine = sel.Object.getGeoVertexIndex(numInName - 1)
typeIdGeometry = mySketch.Geometry[indexObjectHavingPoint].TypeId
if mySketch.Geometry[indexObjectHavingPoint].TypeId == 'Part::GeomLineSegment' :
if indexExtremiteLine == 1 :
x = mySketch.Geometry[indexObjectHavingPoint].StartPoint.x
y = mySketch.Geometry[indexObjectHavingPoint].StartPoint.y
if indexExtremiteLine == 2:
x = mySketch.Geometry[indexObjectHavingPoint].EndPoint.x
y = mySketch.Geometry[indexObjectHavingPoint].EndPoint.y
if mySketch.Geometry[indexObjectHavingPoint].TypeId == 'Part::GeomPoint' :
x = mySketch.Geometry[indexObjectHavingPoint].X
y = mySketch.Geometry[indexObjectHavingPoint].Y
# we select a vertex Circle (so the center)
if mySketch.Geometry[indexObjectHavingPoint].TypeId in ['Part::GeomCircle', 'Part::GeomArcOfCircle'] :
x = mySketch.Geometry[indexObjectHavingPoint].Location.x
y = mySketch.Geometry[indexObjectHavingPoint].Location.y
if typeInSubElementName == 'Constraint' and len(sel.SubElementNames) == 1 :
indexConstraint = numInName - 1
indexObjectHavingPoint = indexConstraint
typeIdGeometry = 'Constraint'
return typeIdGeometry, typeInSubElementName, indexObjectHavingPoint, indexExtremiteLine, x ,y
##########################################
# function returning selected objects at GUI level
# = Sketch, SpreadSheet ....
# parameter :
# '' = no filter
# 'Spreadsheet::Sheet' for spreadsheets only
# 'Sketcher::SketchObject' for sketches etc...
# output: an array of sketch objects, spreadsheets etc.
##########################################
def getGuiObjsSelect(type = ''):
tabGObjSelect = []
selections = Gui.Selection.getCompleteSelection()
for sel in (selections):
if hasattr(sel, 'Object'): # depend freecad version
if type == '' or sel.Object.TypeId == type :
tabGObjSelect.append(sel.Object)
else :
obj=App.ActiveDocument.getObject(sel.Name)
if type == '' or obj.TypeId == type :
tabGObjSelect.append(obj)
return tabGObjSelect
##########################################
# Main proceddure
##########################################
def main():
#initialization
sheckBoxConstraintConflicState = False
indexConstraint = -1
if isExistSpreadSheet() in ['no', 'new'] :
return
# have a look if user wants alias
if importAliasOk :
if setAlias():
return
try :
mySketch = ActiveSketch
except :
QtWidgets.QMessageBox.information(None, "Warning", "Select object must be done in edition mode")
activateSketchEditingWindow()
return
mySketchName = mySketch.Name #actually not use
# Part SpreadSheet
#---------------------------------
sheets = getGuiObjsSelect('Spreadsheet::Sheet')
for sheet in sheets :
Gui.Selection.removeSelection(FreeCAD.ActiveDocument.Name, sheet.Name)
try :
mySpreadSheet = Gui.ActiveDocument.ActiveView.getSheet()
except :
QtWidgets.QMessageBox.information(None, "Warning",
"1- Select a line or 2 points" +
"\n 2- go to a spreadsheet" +
"\n 3- select the cell containing the value." +
"\n 4- stay in the spreadsheet and launch the macro")
activateSketchEditingWindow()
return
mySpreadSheetName = mySpreadSheet.Name
# select the Spreadsheet To be able to retrieve the selected cell :
mySpreadSheet.ViewObject.doubleClicked()
# retrieve the selected cell
ci = lambda :Gui.getMainWindow().centralWidget().activeSubWindow().widget().findChild(QtGui.QTableView).currentIndex()
cellCode = '{}{}{}'.format(chr(ci().column()//28 + 64) if ci().column()//26 > 0 else '', chr(ci().column()%26 + 65), ci().row() + 1)
try:
cellContents = float(mySpreadSheet.get(cellCode))
except:
QtWidgets.QMessageBox.information(None, "Warning",
"Click on a cell with a numeric value before runing the macro")
return
cellAlias = App.ActiveDocument.getObject(mySpreadSheetName).getAlias(cellCode)
# Part sketch
#----------------------------
sels = Gui.Selection.getSelectionEx()
if len(sels) == 0 or len(sels[0].SubElementNames) == 0 :
QtWidgets.QMessageBox.information(None, "Warning", "Anathing is select.\n" +
"Select 1 line, 2 points or a constraint in a sketch before selecting a cell in the spreadsheet")
activateSketchEditingWindow()
return
elif len(sels[0].SubElementNames) > 2 :
QtWidgets.QMessageBox.information(None, "Warning", "Too many objects selected.\n" +
"Select 1 line, 2 points or a constraint in a sketch before selecting a cell in the spreadsheet")
activateSketchEditingWindow()
return
else :
# only one obj selected
#------------------------
if len(sels[0].SubElementNames) == 1 : # only one obj selected
#startPoint of the line
(typeIdGeometry1, typeInSubElementName1, indexObjectHavingPoint1, indexExtremiteLine1, x1 ,y1)=featuresObjSelected (ActiveSketch, sels[0], 0, 1)
if typeInSubElementName1 == 'Constraint' and len(sels[0].SubElementNames) == 1 :
indexConstraint=indexObjectHavingPoint1
elif typeIdGeometry1 == 'Part::GeomLineSegment' :
(typeIdGeometry2, typeInSubElementName2, indexObjectHavingPoint2, indexExtremiteLine2, x2 ,y2) = featuresObjSelected (ActiveSketch, sels[0], 0, 2)
# two obj selected
#------------------------
if len(sels[0].SubElementNames) == 2: # two obj selected
(typeIdGeometry1,typeInSubElementName1, indexObjectHavingPoint1, indexExtremiteLine1, x1 ,y1) = featuresObjSelected (ActiveSketch, sels[0], 0, 1)
(typeIdGeometry2,typeInSubElementName2, indexObjectHavingPoint2, indexExtremiteLine2, x2 ,y2) = featuresObjSelected (ActiveSketch, sels[0], 1, 1)
if ((typeInSubElementName1 not in ('Vertex', 'RootPoint') or typeInSubElementName2 not in ('Vertex', 'RootPoint'))
and not(typeIdGeometry1 in ('Part::GeomCircle', 'Part::GeomArcOfCircle') and typeInSubElementName1 in ['Edge'])) :
QtWidgets.QMessageBox.information(None, "Warning", "2 objects are selected but not 2 points .\n" +
"Select 1 line, 2 points or a constraint in a sketch before selecting a cell in the spreadsheet")
activateSketchEditingWindow()
return
#--------------------------------------
# line or points have been selected have a look if we need to swap points
# -------------------------------------
if ((len(sels[0].SubElementNames) == 1 and typeIdGeometry1 in['Part::GeomLineSegment'] )
or (len(sels[0].SubElementNames) == 2 and typeIdGeometry1
in['Part::GeomLineSegment', 'Part::GeomCircle', 'Part::GeomArcOfCircle', 'Part::GeomPoint'] )) :
# ask the user what kind of constraint he wants
#------------------------------------
# to give focus on the good button
# (Button DistanceX if the two points are more horizontal than vertical)
if abs(x1 - x2) > abs(y1 - y2) :
buttonHavingFocus = 'DistanceX'
else :
buttonHavingFocus = 'DistanceY'
form = getConstraintType(buttonHavingFocus)
form.exec_()
# is the checkboxSheced ?
sheckBoxConstraintConflicState = form.getCheckBoxState()
if form.choiceConstraint in ('Cancel', '') :
activateSketchEditingWindow()
return
myConstraint = form.choiceConstraint # 'DistanceX' or 'DistanceY' or 'Distance'
if (myConstraint == 'DistanceX' and x1 > x2) or (myConstraint == 'DistanceY' and y1 > y2) :
indexObjectHavingPoint1, indexObjectHavingPoint2 = indexObjectHavingPoint2, indexObjectHavingPoint1
indexExtremiteLine1, indexExtremiteLine2 = indexExtremiteLine2, indexExtremiteLine1
# create constraint
#=================================
if cellAlias == None :
cellExpression = mySpreadSheetName + '.' + cellCode
else :
cellExpression = mySpreadSheetName + '.' + cellAlias
if (len(sels[0].SubElementNames) == 1 and typeIdGeometry1 in['Part::GeomCircle', 'Part::GeomArcOfCircle'] ) :
indexConstraint = mySketch.addConstraint(Sketcher.Constraint('Diameter', indexObjectHavingPoint1, cellContents))
elif typeIdGeometry1 != 'Constraint' : # no selected constraint, just line or points
#create the constraint
indexConstraint = mySketch.addConstraint(Sketcher.Constraint(myConstraint
, indexObjectHavingPoint1, indexExtremiteLine1, indexObjectHavingPoint2, indexExtremiteLine2, cellContents))
# for all type except ExternalEdge, set the constraint'formula' (ex : 'spreadSheet.unAlias')
if typeInSubElementName1 not in ['ExternalEdge'] :
mySketch.setExpression('Constraints[' + str(indexConstraint) + ']', cellExpression)
if typeInSubElementName1 in ['ExternalEdge'] and typeIdGeometry1 in ['Part::GeomLineSegment', 'Part::GeomCircle', 'Part::GeomArcOfCircle'] and len(sels[0].SubElementNames) == 1 :
mySketch.setDriving(indexConstraint, False) # External edge constraint is a reference
# put Sketch window ahead
activateSketchEditingWindow()
FreeCADGui.Selection.clearSelection()
# FreeCAD.ActiveDocument.recompute()
ActiveSketch.touch()
ActiveSketch.recompute()
#if Gui.ActiveDocument.getInEdit() == Gui.ActiveDocument.Sketch:
#Gui.ActiveDocument.Sketch.doubleClicked()
# is ther constraintes conflicts ?
if sheckBoxConstraintConflicState :
#if App.activeDocument().isTouched(): # isTouched is not ok in Daily Freecad
if 'Invalid' in mySketch.State :
a = QtWidgets.QMessageBox.question(None, "",
"Constraints conflic detected. Cancel constraint creation ? ",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if a == QtWidgets.QMessageBox.Yes:
mySketch.delConstraint(indexConstraint)
FreeCAD.ActiveDocument.recompute()
activateSketchEditingWindow()
return
if __name__ == '__main__':
main()
Last edited by 2cv001 on Wed Mar 22, 2023 3:31 pm, edited 2 times in total.
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
Re: New Macro " Sketch Constraint From Spreadsheet"
To make it easier to explain the readability changes, I've made a temporary Github branch on my repository, so you can see the extra spacing just makes it easier to read https://github.com/Syres916/FreeCAD-mac ... e0799fe8fb
You can just copy the raw file from https://raw.githubusercontent.com/Syres ... et.FCMacro
Once you've updated the Wiki with the code, please let me know because the last check I need to go through is the AddonManager install which currently generates an error. The user is therefore left to manually copy the file contents which isn't ideal but it might have been due to the those characters that you removed so I'll need to retest it on Windows and Linux English(UK).
Re: New Macro " Sketch Constraint From Spreadsheet"
Thanks again @Syres !Syres wrote: ↑Wed Mar 22, 2023 11:45 am
To make it easier to explain the readability changes, I've made a temporary Github branch on my repository, so you can see the extra spacing just makes it easier to read https://github.com/Syres916/FreeCAD-mac ... e0799fe8fb
You can just copy the raw file from https://raw.githubusercontent.com/Syres ... et.FCMacro
Once you've updated the Wiki with the code, please let me know because the last check I need to go through is the AddonManager install which currently generates an error. The user is therefore left to manually copy the file contents which isn't ideal but it might have been due to the those characters that you removed so I'll need to retest it on Windows and Linux English(UK).
I changed the code in the previous message.
I couldn't take your code as is because in the meantime I had made some changes, but I think I have corrected my code as you suggested. Maybe I missed some things.
I have updated the wiki code.
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr