se faire son propre "import"

Forum destiné aux questions et discussions en français
Forum rules
Be nice to others! Read the FreeCAD code of conduct!
Post Reply
User avatar
2cv001
Posts: 484
Joined: Wed Jan 01, 2020 9:30 am

se faire son propre "import"

Post by 2cv001 »

Bonjour,
Ma macro en cours d'amélioration commence à faire pas mal de lignes. J'ai des idées pour la compléter, mais pour plus de clarté, j'aimerais que mon fichier ne devienne pas énorme afin de m'y retrouver facilement. Bref, j'aimerais déporter certaines fonctions dans un fichier "bibliothèque" que je placerai dans le même dossier que celui de ma macro.

En regardant sur internet, notamment ici : https://courspython.com/modules.html j'ai testé avec ce code :

Un fichier test2.py qui contient

Code: Select all

def carre(valeur):
    resultat = valeur**2
    return resultat

def cube(valeur):
    resultat = valeur**3
    return resultat
    
et un fichier test.FCMacro qui va importer test2.py et qui contient

Code: Select all


from test2 import carre

a = 5
u = carre(a)
print("le carre vaut", u)
Je mets les deux fichier dans le dossier où je mets habituellement toutes mes macros.

lorsque je lance test.FCMacro, j'obtiens le message d'erreur suivant :

Code: Select all

15:33:56  Traceback (most recent call last):
  File "D:/Datas/data/Imprimante 3D/Freecad/test.FCMacro", line 2, in <module>
    from test2 import carre
<class 'ImportError'>: cannot import name 'carre' from 'test2' (D:\Datas/data/Imprimante 3D/Freecad\test2.py)
On ne peut pas faire cala en FreeCAD ?

Cela dit, mon objectif est de faire en sorte que la macro soit à disposition dans l'addon Manager de FreeCAD. Avoir deux fichiers pour une macro, je suppose que ce n'est pas bon ?

Code: Select all

OS: Windows 10 Version 2009
Word size of FreeCAD: 64-bit
Version: 0.21.0.31340 (Git)
Build type: Release
Branch: master
Hash: 856af2a12287a2d7c0d5ab23646be43af26b14b1
Python 3.10.8, Qt 5.15.4, Coin 4.0.0, Vtk 9.1.0, OCC 7.6.3
Locale: French/France (fr_FR)
Installed mods: 
  * Assembly3 0.11.4
  * Curves 0.6.5
  * fasteners 0.4.26
  * Help 1.0.3
  * ThreadProfile 1.84.0
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
mario52
Veteran
Posts: 4690
Joined: Wed May 16, 2012 2:13 pm

Re: se faire son propre "import"

Post by mario52 »

Bonsoir

pas de problème

Code: Select all

22:30:38  le carre vaut 25
mario
Maybe you need a special feature, go into Macros_recipes and Code_snippets, Topological_data_scripting.
My macros on Gist.github here complete macros Wiki and forum.
User avatar
rockn
Veteran
Posts: 1791
Joined: Wed Sep 28, 2011 10:39 am
Location: Toulouse, France
Contact:

Re: se faire son propre "import"

Post by rockn »

Salut,
Chez moi ça marche
22:38:11 le carre vaut 25

Code: Select all

>>> from test2 import carre
>>> from test2 import carre2
Traceback (most recent call last):
  File "<input>", line 1, in <module>
ImportError: cannot import name 'carre2' from 'test2' (/home/rockn/.local/share/FreeCAD/Macro/test2.py)
>>> 
Tu n'aurais pas simplement fais une faute de frappe ?
Aussi, tes macros ne sont pas dans un dossier Macro ?

Code: Select all

OS: Pop!_OS 22.04 LTS (pop:GNOME/pop)
Word size of FreeCAD: 64-bit
Version: 0.21.0.31917 (Git) AppImage
Build type: Release
Branch: master
Hash: 4639283b459f9ff9093e289908cc003db8745b82
Python 3.10.9, Qt 5.15.6, Coin 4.0.0, Vtk 9.1.0, OCC 7.6.3
Locale: English/United Kingdom (en_GB)
Installed mods: 
  * Assembly4 0.12.6
  * workfeature
  * freecad_metal_workbench 0.0.1
  * pyrate (Disabled)
  * sheetmetal 0.2.61
  * mooc-workbench 2022.4.21
  * BIM 2021.12.0
  * workfeature-macro
  * dodo 1.0.0
  * Curves 0.6.8
Formations - Assistance - Développement : https://freecad-france.com
User avatar
2cv001
Posts: 484
Joined: Wed Jan 01, 2020 9:30 am

Re: se faire son propre "import"

Post by 2cv001 »

Ben mince chez moi non. Il ne doit pas aller chercher le fichier importéau bon endroit. Il va falloir que j'enquete !
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
mario52
Veteran
Posts: 4690
Joined: Wed May 16, 2012 2:13 pm

Re: se faire son propre "import"

Post by mario52 »

Bonjour

essayez avec pour voir :

Code: Select all

import test2
from test2 import carre

a = 5
u = carre(a)
print("le carre vaut", u)
mario
Maybe you need a special feature, go into Macros_recipes and Code_snippets, Topological_data_scripting.
My macros on Gist.github here complete macros Wiki and forum.
User avatar
2cv001
Posts: 484
Joined: Wed Jan 01, 2020 9:30 am

Re: se faire son propre "import"

Post by 2cv001 »

EDIT : ne pas tenir compte de ce message. j'ai tout faux !
Voir message suivant.

J'ai repris mes essais.
Et là ça a fini par marcher.
Ce que je crois : j'avais deux fichier test2. Le bon fichier test2.py et un autre qui trainait par là, test2.FCMacro. Et il allait chercher le mauvais . Il faudrait que je vérifie, mais il irait donc chercher en priorité le fichier se terminant par FCMacro plutôt que par .py

En tout cas merci pour vos réponses car sinon je m'imaginais que FreeCAD ne prenait pas en compte ce type d'apport ou qu'il fallait mettre le fichier importé ailleurs... Bref, grâce à vous, j'ai investigué du bon coté.
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
User avatar
2cv001
Posts: 484
Joined: Wed Jan 01, 2020 9:30 am

Re: se faire son propre "import"

Post by 2cv001 »

Bon, j'avais tout faux dan sle message précédent.
L'extention du fichier importé doit être py et non FCMacro

Le piège des import est qu'une fois en mémoire, la bibliothèque n'est pas de nouveau uploadée lorsque l'on démarre une nouvelle fois la macro.
On a beau modifier le fichier "importé", les modifs ne sont pas prises en compte.


Dans ce cas, pour que les modifs soient prises en compte, il faut faire un reload.

Code: Select all

from importlib import reload 
import test2
reload(test2)
from test2 import carre

a = 5
u = carre(a)
print("le carre vaut", u)


Testé sur un autre code, mais pas directement ce code.
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
User avatar
2cv001
Posts: 484
Joined: Wed Jan 01, 2020 9:30 am

Re: se faire son propre "import"

Post by 2cv001 »

Bon, maintenant, je me pose la question de la mise à disposition du code en question.
Il s'agit donc de cette macro :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
Là, je vais maintenant mettre à jour le code en y ajoutant les fonctions qui permettent de créer automatiquement les alias dans le tableur.
pour l'instant, le code est en deux parties, ce qui m'a permis de tester plus facilement les fonctions ajoutées.
Si je laisse comme ça, il faut que je donne les deux codes (le code principal et le code qui ajoute les fonctions d'alias) avec donc un import dans le code principal. Comme je fais un try sur cet import, si jamais l'utilisateur ne récupère que le fichier principal, ça fonctionne, mais il ne bénéficie pas de tout ce qui concerne la création des alias. Si alors il télécharge le second fichier et il le place dans le même dossier que la macro principale, comme par magie, les fonctions alias sont opérationnelles.
Comme dit plus haut, j'ai préféré faire comme cela pour la mise au point.
Maintenant, la question est : est il mieux que je donne ces deux fichiers séparément ou que j'incorpore le contenu du second fichier dans la macro principale ?
J'ai l'impression que je ferais mieux de faire cette seconde solution. Plus facile à installer pour les utilisateurs, mais donnera un code un peu moins clair, car les deux parties seront moins séparées. Et je pesne qu'une installation en deux fichiers est incompatible avec l'addonManager de FreeCAD

Pour vous donner éventuellement une idée :
La partie principale du code

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...                ==
# =========================================================================
# =========================================================================
__author__ = "2cv001"
__title__   = "Macro Sketch Constraint From Spreadsheet"
__date__    = "2023/03/02"    #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
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
   
    





#=======================================================
# 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()
        #macroDirectory=FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro").GetString("MacroPath")+"\\" # LINUX ?
        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") + "\\"
        try :
            config.read(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):
        macroDirectory = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro").GetString("MacroPath") + "\\"

        # 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)
    print('itemName',itemName)
    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'ou '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 obs selected)
    if typeInSubElementName == 'Edge'and len(sel.SubElementNames)==2:
        indexObjectHavingPoint=numInName-1
        typeIdGeometry=mySketch.Geometry[indexObjectHavingPoint].TypeId
        if typeIdGeometry in  ['Part::GeomCircle'] :
            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)
        print('294 indexObjectHavingPoint',indexObjectHavingPoint)
        print('295 mySketch.Geometry[0].TypeId',mySketch.Geometry[0].TypeId)
        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")
        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")
        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")
        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")
        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 == 'Part::GeomCircle' 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")
                return
            # to do : if one is a circle, reolace with it's center.
            #if typeIdGeometry1 in ('Part::GeomCircle', 'Part::GeomArcOfCircle')





    #--------------------------------------
    # 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','') :
            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()

    return

if __name__ == '__main__':
    main()
procEnd()
et la partie "alias" (à sauver impérativement avec comme nom de fichier : "sketchConstraintFromSpreadsheetAlias.py" attention extension.py
Ce fichier est à placer dans le même dossier que l'autre fichier

Code: Select all


# -*- coding: utf-8 -*-


import FreeCAD as App
import FreeCADGui as Gui
from PySide import QtGui
from PySide2 import QtWidgets


separateur=" "  # classiquement mettre " " ainsi les blancs seront remplacés par nouveauCaract
nouveauCaract=''    #Mettre par exemple "_" pour que les séparateurs soit remplacés par "_". Mettre "" pour ne pas avoir de séparateur
majuscule=True  #mettre à True si on veut que "Diametre du cercle" devienne "DiametreDuCercle"
changeTexteCellule=False # le texte ne sera changé que si changeTexteCellule est à True. Cela ne change rien pour l'allias lui-même
premierCaractereEnMinuscule=True #Force le premier caractère à être en minuscule

# liste des caractères devant être remplacés par un équivalent. par exemple 'é' sera remplacé par 'e'
caracEquivalents =[ ['é','e'],['è','e'],['à','a'],['@','a'],['&','e'],['ç','c'],['²','2'],["'",''] ]

def remplaceCartatParEquivalent(caractere):
# remplace un caractère par son équivalent sil existe
    caracResult=caractere   
    for couple in caracEquivalents:
        if (couple[0]==caractere):
            caracResult=couple[1]
            break
    return caracResult


def remplaceCararcDansMot(mot):
#remplace tous les caractères du mot par son équivalent s'il existe
    motResult=mot
    for caract in mot:
        a=remplaceCartatParEquivalent(caract)
        motResult=motResult.replace(caract,a)
    return motResult



def traitementChaineSource(chaineSource,separateur,nouveauCaract,majuscule):
# Si séparateur  vaut ' ' et nouveauCaract vaut '_', et majuscule vaut True
# transforme "Diametre du cylindre" en "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:]
            # On utilise "[:1]" au lieu de "[0]", car ce dernier plante en cas de chaine vide (ce qui arrive si la cellule est vide)
        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 :
         ### On affiche un dialogue demandant à l'utilisateur s'il est sûr de son coup
        if QtGui.QMessageBox.warning(Gui.getMainWindow(),
        'Warning','You did not select any object in an active sketch.'+ 
        'Does 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() # On stocke la fenêtre active        
    #mySpreadSheet=trouveSheet(aw)
    try :
        mySpreadSheet=Gui.ActiveDocument.ActiveView.getSheet()
    except :
         QtWidgets.QMessageBox.warning(Gui.getMainWindow(), "Warning", "No active spreadsheet with selected cells.")
         return True
            
    sel_items = aw.widget().findChild(QtGui.QTableView).selectedIndexes() # On récupère la liste complète de toutes les cellules sélectionnées

    ### On définit une fonction qui renverra l'identifiant de la cellule à partir de ses numéros de ligne (r) et colonne (c)
    ### Les numéros commencent à 0 pour la première ligne/colonne
    ### On gère correctement les colonnes avec un identifiant à 2 lettres
    cellName = lambda r,c:'{}{}{}'.format(chr(c//26 + 64) if c//26 > 0 else '', chr(c%26+65), r+1)

    # on vérifie qu'une des cellules n'est pas un numérique. Sinon, message et on sort
    for item in sel_items: # On parcourt les cellules sélectionnées
        cell = cellName(item.row(),item.column()) # On récupère l'identifiant de la cellule
        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. Try again after correction")
            return False
         
        
    for item in sel_items: # On parcourt les cellules sélectionnées
        cell = cellName(item.row(),item.column()) # On récupère l'identifiant de la cellule
        next_cell = cellName(item.row(), item.column()+1) # On récupère l'identifiant de la cellule voisine à droite
        activeCellContenu=mySpreadSheet.getContents(cell)
        # traitement de la chaine de caractère contenue dans la cellule
        activeCellContenu=traitementChaineSource(activeCellContenu,separateur,nouveauCaract,majuscule)
        if changeTexteCellule:# si le paramètre changeTexteCellule est à True alors on remplace le texte de la cellule
            mySpreadSheet.set(cell, activeCellContenu)
        alias=activeCellContenu

        try: # Bloc try pour récupérer les erreurs
            mySpreadSheet.setAlias(next_cell, alias) # On attribue l'alias à la cellule voisine de droite

        except ValueError: # Si une erreur "ValueError" est déclenchée (ce qui arrive quand l'alias n'est pas valide)
            App.Console.PrintWarning("Can't set alias for cell {} : {} isn't valid\n".format(next_cell, alias)) # On prévient l'utilisateur dans le rapport
    return True        
if __name__ == '__main__':
    setAlias()
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
User avatar
flachyjoe
Veteran
Posts: 1891
Joined: Sat Mar 31, 2012 12:00 pm
Location: Limoges, France

Re: se faire son propre "import"

Post by flachyjoe »

Salut,

c'est un peu de la bidouille mais tu peux empaqueter le code du .py dans le .FCMacro et écrire le script sur le disque à la demande.

Exemple avec ma macro Family

Code: Select all

# Augmented array by FlachyJoe
# v0.3 05-16-22

import os.path
macro_path = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro").GetString('MacroPath')

Family_py = r"""
# Augmented array by FlachyJoe
# v0.3 05-16-22

from FreeCAD import Console, Units
import FreeCADGui as Gui
import Part

CONSTRAINT_PREFIX = 'Constraint_'
PROPERTY_LISTS  = {
	'App::PropertyBool'		: 'App::PropertyBoolList',
	'App::PropertyFloat'	: 'App::PropertyFloatList',
	'App::PropertyFloatList'	: None,
	'App::PropertyFloatConstraint'	: 'App::PropertyFloatList',
	'App::PropertyPrecision'	: 'App::PropertyFloatList',
	'App::PropertyQuantity'	: 'App::PropertyFloatList',
	'App::PropertyQuantityConstraint'	: 'App::PropertyFloatList',
	'App::PropertyAngle'	: 'App::PropertyFloatList',
	'App::PropertyDistance'	: 'App::PropertyFloatList',
	'App::PropertyLength'	: 'App::PropertyFloatList', 
	'App::PropertyArea'	: 'App::PropertyFloatList',
	'App::PropertyVolume'	: 'App::PropertyFloatList',
	'App::PropertyFrequency'	: 'App::PropertyFloatList',
	'App::PropertySpeed'	: 'App::PropertyFloatList',
	'App::PropertyAcceleration'	: 'App::PropertyFloatList',
	'App::PropertyForce'	: 'App::PropertyFloatList',
	'App::PropertyPressure'	: 'App::PropertyFloatList',
	'App::PropertyElectricPotential'	: 'App::PropertyFloatList',
	'App::PropertyVacuumPermittivity'	: 'App::PropertyFloatList',
	'App::PropertyInteger'	: 'App::PropertyIntegerList',
	'App::PropertyIntegerConstraint'	: 'App::PropertyIntegerList',
	'App::PropertyIntegerSet'	: None,
	'App::PropertyPercent'	: 'App::PropertyFloatList',
	'App::PropertyEnumeration'	: None,
	'App::PropertyIntegerList'	: None,
	'App::PropertyMap'		: None,
	'App::PropertyString'	: 'App::PropertyStringList',
	'App::PropertyPersistentObject'	: None,
	'App::PropertyUUID'	: None,
	'App::PropertyFont'	: None,
	'App::PropertyLink'	: 'App::PropertyLinkList',
	'App::PropertyLinkChild'		: 'App::PropertyLinkListChild',
	'App::PropertyLinkListChild'	: None,
	'App::PropertyLinkGlobal'	: 'App::PropertyLinkListGlobal',
	'App::PropertyLinkHidden'	: 'App::PropertyLinkListHidden' , 
	'App::PropertyLinkSub'	: 'App::PropertyLinkSubList', 
	'App::PropertyLinkSubChild'	: 'App::PropertyLinkSubListChild' , 
	'App::PropertyLinkSubGlobal'	: 'App::PropertyLinkSubListGlobal',
	'App::PropertyLinkSubHidden'	:  'App::PropertyLinkSubListHidden', 
	'App::PropertyLinkListGlobal'		: None,
	'App::PropertyLinkListHidden'	: None,
	'App::PropertyLinkSubList'	: None,
	'App::PropertyLinkSubListChild'	: None,
	'App::PropertyLinkSubListGlobal'	: None,
	'App::PropertyLinkSubListHidden'	: None,
	'App::PropertyXLink'	: 'App::PropertyXLinkList',
	'App::PropertyXLinkSub'	: 'App::PropertyXLinkSubList',
	'App::PropertyXLinkSubList'	: None,
	'App::PropertyXLinkList'	: None,
	'App::PropertyMatrix'	: None,
	'App::PropertyVector'	: 'App::PropertyVectorList',
	'App::PropertyVectorDistance'	: 'App::PropertyFloatList',
	'App::PropertyPosition'	: 'App::PropertyVectorList',
	'App::PropertyDirection'	: 'App::PropertyVectorList',
	'App::PropertyVectorList'	: None,
	'App::PropertyPlacement'	: 'App::PropertyPlacementList',
	'App::PropertyPlacementList'		: None,
	'App::PropertyPlacementLink'	: None,
	'App::PropertyRotation'	: None, 
	'App::PropertyColor'	: 'App::PropertyColorList',
	'App::PropertyColorList'	 : None,
	'App::PropertyMaterial'	: 'App::PropertyMaterialList',
	'App::PropertyMaterialList'	: None,
	'App::PropertyPath'	: 'App::PropertyStringList',
	'App::PropertyFile'	: 'App::PropertyStringList',
	'App::PropertyFileIncluded'	: None,
	'App::PropertyPythonObject'	: None,
	'App::PropertyExpressionEngine'	: None,
}

class Family:
	def __init__(self, obj, base=None):
		'''Add some custom properties to our family feature'''
		obj.addProperty("App::PropertyLink","Base","Family","Object to duplicate")
		obj.addProperty("App::PropertyLink","PropertyOwner","Family","Object to change (Base if not set)")
		obj.addProperty("App::PropertyEnumeration","Property","Family","Property to change between instance")
		obj.addProperty("App::PropertyLinkList","Table","Family","Values for property")
		obj.addProperty("App::PropertyVectorDistance","Interval","Family","Distance between instance")
		
		if base:
			obj.Base = base
			self.onChanged(obj, 'Base')

		obj.Proxy = self
		self.subject = obj.Base

	def onChanged(self, fp, prop):
		if prop == 'Base' or prop=='PropertyOwner':
			if fp.PropertyOwner:
				self.subject =  fp.PropertyOwner
			else:
				self.subject =  fp.Base
			if self.subject:
				self.PropertiesList = self.subject.PropertiesList
	
				if self.subject.TypeId  == 'Sketcher::SketchObject':
					for i, cstrt in enumerate(self.subject.Constraints):
						if cstrt.Type in ['Angle', 'Radius', 'Diameter', 'DistanceX', 'DistanceY', 'Distance', 'SnellsLaw']:
							if cstrt.Name:
								self.PropertiesList.append(f"{CONSTRAINT_PREFIX}{cstrt.Name}")
							else:
								self.PropertiesList.append(f"{CONSTRAINT_PREFIX}{i}")
				fp.Property = self.PropertiesList

		elif prop=='Property':
			if (not self.subject or not fp.Property or not hasattr(self.subject, fp.Property)):
				return
			try :
				typeId = self.subject.getTypeIdOfProperty(fp.Property)
				try:
					listTypeId = PROPERTY_LISTS[typeId]
				except Exception as e:
					Console.PrintLog(f"0 {e}\n")

				if not listTypeId:
					return

				if hasattr(fp, 'Table') and fp.getTypeIdOfProperty('Table') == listTypeId:
					return
	
				if hasattr(fp,'Table'):
					fp.removeProperty('Table')
				try:
					fp.addProperty(listTypeId, "Table", "Family", "Values for property")
				except Exception as e:
					Console.PrintLog(f"{e}\n")
			except Exception as e:
				Console.PrintLog(f"1 {e}\n")		
	
	def execute(self, fp):
		cstrtName = ""
		try :
			if self.subject.TypeId == 'Sketcher::SketchObject' and fp.Property.startswith(CONSTRAINT_PREFIX):
				cstrtName = fp.Property[len(CONSTRAINT_PREFIX):]
				if cstrtName.isnumeric():
					cstrtName = int(cstrtName)
				initial = self.subject.getDatum(cstrtName)
			else:
				initial = getattr(self.subject, fp.Property)
		except Exception as e:
			Console.PrintLog(f"2 {e}\n")

		try:
			results=[]
			for id, value in enumerate(fp.Table):
				if cstrtName:
					self.subject.setDatum(cstrtName, Units.Quantity(value).Value)
				else:
					setattr(self.subject, fp.Property, value)
				for obj in fp.Base.OutList:
					obj.recompute()
				fp.Base.recompute()

				res = fp.Base.Shape.copy()
				res.translate(fp.Interval * id)
				results.append(res)
			fp.Shape = Part.makeCompound(results)
		
			if cstrtName:
				self.subject.setDatum(cstrtName, Units.Quantity(initial).Value)
			else:
				setattr(self.subject, fp.Property, initial)

			for obj in fp.Base.OutList:
				obj.recompute()
				obj.purgeTouched()
			fp.Base.recompute()
			fp.Base.purgeTouched()
			Gui.updateGui()

		except Exception as e:
			Console.PrintLog(f"3 {e}\n")

	def __getstate__(self):
		return None
	
	def __setstate__(self, state):
		return None
###
"""

# write the Family.py file in the macro directory
with open(os.path.join(macro_path, "Family.py"), 'w') as file:
	file.write(Family_py)


from Family import Family

def makeFamily():
	a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Family")
	base = None
	sel = Gui.Selection.getSelection()
	if sel:
		base =  sel[0]
	Family(a, base)
	a.ViewObject.Proxy=0

makeFamily()

Note le préfixe r pour que les séquences d’échappements \n ne soit pas interprétés dans le texte.
- Flachy Joe -
Image
User avatar
2cv001
Posts: 484
Joined: Wed Jan 01, 2020 9:30 am

Re: se faire son propre "import"

Post by 2cv001 »

Effectivement, c'est une astuce...
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
Post Reply