Can Spreadsheet WB make list of parts and sub-assemblies?

Post here for help on using FreeCAD's graphical user interface (GUI).
Forum rules
and Helpful information
IMPORTANT: Please click here and read this first, before asking for help

Also, be nice to others! Read the FreeCAD code of conduct!
Post Reply
User avatar
Roland
Posts: 333
Joined: Fri Aug 21, 2015 2:20 pm

Can Spreadsheet WB make list of parts and sub-assemblies?

Post by Roland »

Hi Forum,

I want to make a list of all Parts and/or Sub-Assemblies inside one Assembly.

Forum member Triplus guided me to Arch Schedule. That one is able to count all occurrences of individual Labels, such as 'window'. Thereby, the Label name is input, and the number of occurrences is output. It is not what I need. Because:
1/ A list of parts does not know before hand which Labels are to be expected.
2/ Not all Labels should be counted, only those that represent a Part or Sub-Assembly.

Any solutions available?

Greetz,

Roland
User avatar
Roland
Posts: 333
Joined: Fri Aug 21, 2015 2:20 pm

Re: Can Spreadsheet WB make list of parts and sub-assemblies?

Post by Roland »

One attempt ...

A list of parts is created by Assembly2 WB as a drawing object, and also created as an object in the console. Can we use this to get there?

The attempt: Use Assembly2 WB, make new object of 2 parts. Create muxedAssembly (a new, invisible object, but needed for an assembly drawing). Then, inside spreadsheet read data from that Mux, e.g. Placement, Type, Label. That all works fine, indeed.
And then, there is one property that I want to be brought to the spreadsheet: muxedObjectList. It is 'seen' by the spreadsheet command list as soon as one types the first two characters (cell contents =muxedAssembly.muxedObjectList) , and then merely returns the command given as cell contents when executed. And the '=' sign gets deleted by FC. When '=' deliberately reinserted, FC returns ERR. So, it is sort of an empty command.

(By the way, such ObjectList is created by muxing in Assembly2, but rather not by Make Compound in Part WB.)

What can we do?

Roland
julieAP
Posts: 30
Joined: Tue Sep 12, 2017 9:18 am

Re: Can Spreadsheet WB make list of parts and sub-assemblies?

Post by julieAP »

Hello !

I am facing the same issue. For now, i use Part List in Assembly2 to get a tab on a drawing with all the parts and the number of occurence. However i would like to have this tab in a spreadsheet, to be able to get some info on every part, such as length.

It looks like muxedObjectList would be the appropriate way to have that, but as you said it looks like an empty command.
L'Atelier Paysan, Coopérative d'autoconstruction de machines agricoles
Tous les plans des outils sont disponibles en opensource, sur https://www.latelierpaysan.org/Plans-et-Tutoriels
thschrader
Veteran
Posts: 3157
Joined: Sat May 20, 2017 12:06 pm
Location: Germany

Re: Can Spreadsheet WB make list of parts and sub-assemblies?

Post by thschrader »

julieAP wrote: Fri Sep 15, 2017 12:33 pm Hello !

I am facing the same issue. For now, i use Part List in Assembly2 to get a tab on a drawing with all the parts and the number of occurence. However i would like to have this tab in a spreadsheet, to be able to get some info on every part, such as length.

It looks like muxedObjectList would be the appropriate way to have that, but as you said it looks like an empty command.
Hello julieAP,
I had a look on your website here
https://www.latelierpaysan.org/Four-a-pain-2515

Very interesting!
Do you use FreeCAD for designing your projects?
(printing style looks like techdraw-workbench...)
regards Thomas
julieAP
Posts: 30
Joined: Tue Sep 12, 2017 9:18 am

Re: Can Spreadsheet WB make list of parts and sub-assemblies?

Post by julieAP »

Hi !

I'm happy you like it ! Right now we use SolidWorks, but we plan to make the transition to FreeCad :) it would be more in agreement with our values to use an open source software. I am here to work on that !
I will pretty soon post something on the forum to present us in more details.

Back to the topic, flachyjoe posted this macro on the french forum, it seems to work pretty fine to make a list in a spreadsheet of the visible objects of the document and extract their properties (length...)

Code: Select all

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

import re	#support des expressions régulières

LABEL="Composant"
COUNT="Nombre"

DEBUG = False
def Debug(text):
	if DEBUG :
		Log(text+'\n')

def isAdditiveFunc(obj) :
	'''L'objet corespond à un ajout de matière'''
	return 'AddShape' in obj.PropertiesList

def isArray(obj) :
	return 'ArrayType' in obj.PropertiesList

def isSketch(obj) :
	return obj.TypeId=='Sketcher::SketchObject'

def getProperties(obj, prop={}) :
	'''Extraits les informations sur un objet en parcourant récursivement son arbre de dépendence'''

	Debug('get properties '+obj.Label)

	if isAdditiveFunc(obj) :
		if 'Length' in obj.PropertiesList and not prop.has_key('Length') :
			prop['Length'] = obj.Length
		if 'Angle' in obj.PropertiesList and not prop.has_key('Angle') :
			prop['Angle'] = obj.Angle
		
	if isArray(obj) :
		if obj.ArrayType=='ortho' :
			prop[COUNT] = obj.NumberX * obj.NumberY * obj.NumberZ
		else :
			prop[COUNT] = obj.NumberPolar
	if isSketch(obj) :
		#Extrait les valeurs des contraintes nommées
		for c in obj.Constraints :
			if c.Name :
				prop[c.Name]= re.search('Value="([^"]*)', c.Content).group(1)

	for child in obj.OutList :
		prop = getProperties(child, prop)
	return prop

def createDict(doc):
	'''Crée un dictionnaire associant le nom de chaque objet à ses propriétés'''
	objs = doc.Objects
	
	#resultat
	listObjects = {}

	for obj in objs:
		if not obj.ViewObject.Visibility :
			#On ne liste pas les objets cachés
			continue
	
		prop = getProperties(obj, {})

		if not prop.has_key(COUNT) : 
			prop[COUNT] = 1

		lbl = obj.Label
		if isArray(obj) :
			lbl = obj.Base.Label
		lbl = re.sub('_[^_]*$', '', lbl)
		lbl = re.sub('^Clone of ', '', lbl)

		count1 = prop[COUNT]
		del prop[COUNT]

		added = False
		#si le nom existe deja dans le dictionnaire
		while listObjects.has_key(lbl) and not added :
			#recupere les infos sur l'objet stocké
			objInfos = listObjects[lbl]
			#On ne prend pas en compte la quantité d'objet pour comparer
			count0 = listObjects[lbl][COUNT]
			del listObjects[lbl][COUNT]
			#On test si l'objet courant a les même prpriétés que celui déjà stocké
			k0 = zip(objInfos.keys(), objInfos.values())
			k0.sort(key=lambda x: x[0])
			k1 = zip(prop.keys(), prop.values())
			k1.sort(key=lambda x: x[0])

			if k0 == k1 :
				#Ils ont les mêmes propriétés, on additionne les quantités
				listObjects[lbl][COUNT] = count0 + count1
				added = True
			else :
				#ils n'ont pas les mêmes propriétés
				#iteration du nombre final du nom
				index = int('0'+re.search('[0-9]*$', lbl).group())
				if index == 0 :
					lbl = lbl + '_1'
				else :
					lbl = re.sub('[0-9]*$', str(index + 1), lbl)
				
		if not added :
			prop[COUNT] = count1
			listObjects[lbl] = prop

	return listObjects

def columnName(n) :
	'''Renvoi le nom de la colone à partir de son indice 0->A, 27->AB ... 675->ZZ '''
	if n < 26 :
		return chr(ord('A')+n)
	elif n < 26*26 :
		return chr(ord('A')+int(n / 26)) + chr(ord('A')+(n % 26))
	raise Exception('Out of range')

def fillSpreadsheet(listObjects, spreadsheet) :
	'''Rempli le tableur à partir d'un dictionnaire'''
	#Liste de toutes les propriétés parmi les objets
	keys = []
	for props in listObjects.values() :
		keys.extend(props.keys())

	#Compte les noms des propriétés
	uniq_keys = {}
	added = set()
	nums = {}
	for val in keys :
		if not val in added:
			uniq_keys[val] = 1
			added.add(val)
		else :
			uniq_keys[val] += 1

	#Tri par ordre d'utilisation
	from collections import OrderedDict
	from operator import itemgetter
	header = OrderedDict(sorted(uniq_keys.items(), key=itemgetter(1))).keys()
	header.reverse()

	#Création de l'entête
	spreadsheet.set('A1', LABEL)
	x=1
	for prop in header :
		spreadsheet.set(columnName(x)+'1', prop)
		x = x + 1
	spreadsheet.setStyle('A1:ZZ1', 'bold', 'add')	

	#remplissage
	y=2
	for lbl in listObjects :
		spreadsheet.set('A'+str(y), lbl.encode('utf-8'))
		x=1
		for prop in header :
			if listObjects[lbl].has_key(prop) :
				spreadsheet.set(columnName(x)+str(y), str(listObjects[lbl][prop]))
			x = x + 1
		y = y + 1


doc = FreeCAD.ActiveDocument
doc.openTransaction("Nomenclature")
fillSpreadsheet(createDict(doc), doc.addObject('Spreadsheet::Sheet','Nomenclature'))
doc.commitTransaction()
doc.recompute()
If needed i can translate the comments inside the code :)

Julie
Last edited by julieAP on Wed Sep 20, 2017 1:27 pm, edited 1 time in total.
L'Atelier Paysan, Coopérative d'autoconstruction de machines agricoles
Tous les plans des outils sont disponibles en opensource, sur https://www.latelierpaysan.org/Plans-et-Tutoriels
User avatar
r-frank
Veteran
Posts: 2180
Joined: Thu Jan 24, 2013 6:26 pm
Location: Möckmühl, Germany
Contact:

Re: Can Spreadsheet WB make list of parts and sub-assemblies?

Post by r-frank »

Hello.

Just for completeness, the macro dump objects does something similar ...

Roland
Deutsche FreeCAD Tutorials auf Youtube
My GrabCAD FreeCAD-Projects
FreeCAD lessons for beginners in english

Native german speaker - so apologies for my english, no offense intended :)
User avatar
easyw-fc
Veteran
Posts: 3633
Joined: Thu Jul 09, 2015 9:34 am

Re: Can Spreadsheet WB make list of parts and sub-assemblies?

Post by easyw-fc »

julieAP wrote: Mon Sep 18, 2017 12:07 pm If needed i can translate the comments inside the code :)
Julie
that would be nice ...
thx for linking the code :D
julieAP
Posts: 30
Joined: Tue Sep 12, 2017 9:18 am

Re: Can Spreadsheet WB make list of parts and sub-assemblies?

Post by julieAP »

Here you are, i hope the translation is understandable:

Code: Select all

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

import re	#support des expressions régulières

LABEL="Component"
COUNT="Number"

DEBUG = False
def Debug(text):
	if DEBUG :
		Log(text+'\n')

def isAdditiveFunc(obj) :
	'''The object corresponds to an addition of matter'''
	return 'AddShape' in obj.PropertiesList

def isArray(obj) :
	return 'ArrayType' in obj.PropertiesList

def isSketch(obj) :
	return obj.TypeId=='Sketcher::SketchObject'

def getProperties(obj, prop={}) :
	'''Extracts the information of an object by travelling recursively its tree of dependence'''

	Debug('get properties '+obj.Label)

	if isAdditiveFunc(obj) :
		if 'Length' in obj.PropertiesList and not prop.has_key('Length') :
			prop['Length'] = obj.Length
		if 'Angle' in obj.PropertiesList and not prop.has_key('Angle') :
			prop['Angle'] = obj.Angle
		
	if isArray(obj) :
		if obj.ArrayType=='ortho' :
			prop[COUNT] = obj.NumberX * obj.NumberY * obj.NumberZ
		else :
			prop[COUNT] = obj.NumberPolar
	if isSketch(obj) :
		#Extracts the values of the named constraints
		for c in obj.Constraints :
			if c.Name :
				prop[c.Name]= re.search('Value="([^"]*)', c.Content).group(1)

	for child in obj.OutList :
		prop = getProperties(child, prop)
	return prop

def createDict(doc):
	'''Create a dictionary linking the name of each object to its properties'''
	objs = doc.Objects
	
	#resultat
	listObjects = {}

	for obj in objs:
		if not obj.ViewObject.Visibility :
			#On ne liste pas les objets cachés
			continue
	
		prop = getProperties(obj, {})

		if not prop.has_key(COUNT) : 
			prop[COUNT] = 1

		lbl = obj.Label
		if isArray(obj) :
			lbl = obj.Base.Label
		lbl = re.sub('_[^_]*$', '', lbl)
		lbl = re.sub('^Clone of ', '', lbl)

		count1 = prop[COUNT]
		del prop[COUNT]

		added = False
		#if the name already exists in the dictionary
		while listObjects.has_key(lbl) and not added :
			#retrieves the information of the stored object
			objInfos = listObjects[lbl]
			#We do not consider the amount of object to compare
			count0 = listObjects[lbl][COUNT]
			del listObjects[lbl][COUNT]
			#We test if the current object has the same properties as the one already stored
			k0 = zip(objInfos.keys(), objInfos.values())
			k0.sort(key=lambda x: x[0])
			k1 = zip(prop.keys(), prop.values())
			k1.sort(key=lambda x: x[0])

			if k0 == k1 :
				#They have the same properties, so we add the quantities
				listObjects[lbl][COUNT] = count0 + count1
				added = True
			else :
				#they don't have the same properties
				#Iteration of the final number of the name
				index = int('0'+re.search('[0-9]*$', lbl).group())
				if index == 0 :
					lbl = lbl + '_1'
				else :
					lbl = re.sub('[0-9]*$', str(index + 1), lbl)
				
		if not added :
			prop[COUNT] = count1
			listObjects[lbl] = prop

	return listObjects

def columnName(n) :
	'''Gives the name of the column using its index 0->A, 27->AB ... 675->ZZ '''
	if n < 26 :
		return chr(ord('A')+n)
	elif n < 26*26 :
		return chr(ord('A')+int(n / 26)) + chr(ord('A')+(n % 26))
	raise Exception('Out of range')

def fillSpreadsheet(listObjects, spreadsheet) :
	'''Files the spreadsheet from a dictionary'''
	#Makes a list of every properties in the objects
	keys = []
	for props in listObjects.values() :
		keys.extend(props.keys())

	#Compute the names of the properties
	uniq_keys = {}
	added = set()
	nums = {}
	for val in keys :
		if not val in added:
			uniq_keys[val] = 1
			added.add(val)
		else :
			uniq_keys[val] += 1

	#Sorting by order of use
	from collections import OrderedDict
	from operator import itemgetter
	header = OrderedDict(sorted(uniq_keys.items(), key=itemgetter(1))).keys()
	header.reverse()

	#Creation of the heading
	spreadsheet.set('A1', LABEL)
	x=1
	for prop in header :
		spreadsheet.set(columnName(x)+'1', prop)
		x = x + 1
	spreadsheet.setStyle('A1:ZZ1', 'bold', 'add')	

	#filling
	y=2
	for lbl in listObjects :
		spreadsheet.set('A'+str(y), lbl.encode('utf-8'))
		x=1
		for prop in header :
			if listObjects[lbl].has_key(prop) :
				spreadsheet.set(columnName(x)+str(y), str(listObjects[lbl][prop]))
			x = x + 1
		y = y + 1


doc = FreeCAD.ActiveDocument
doc.openTransaction("Nomenclature")
fillSpreadsheet(createDict(doc), doc.addObject('Spreadsheet::Sheet','Nomenclature'))
doc.commitTransaction()
doc.recompute()
L'Atelier Paysan, Coopérative d'autoconstruction de machines agricoles
Tous les plans des outils sont disponibles en opensource, sur https://www.latelierpaysan.org/Plans-et-Tutoriels
User avatar
easyw-fc
Veteran
Posts: 3633
Joined: Thu Jul 09, 2015 9:34 am

Re: Can Spreadsheet WB make list of parts and sub-assemblies?

Post by easyw-fc »

julieAP wrote: Mon Sep 18, 2017 1:50 pm Here you are...
many thanks! :D
Post Reply