In openPLM, all parts have a bill of materials (BOM) which lists the children parts that compose a part. A BOM lists for each child part how much is needed to build or assembly the parent part.
openPLM manages single-line BOM (first level of children) and indented BOM (all levels of children).
openPLM stores each row of a BOM but it only stores the first level. Indented BOM are dynamically built.
For example, the following (simplified) BOM is stored with 3 rows.
Level | Element | Quantity |
---|---|---|
— | P0 | — |
1 | P1 | 2 |
2 | P2 | 4 |
1 | P2 | 5 |
The rows are (notation: parent, child, quantity):
- P0, P1, 2
- P1, P2, 4
- P0, P2, 5
The model behind these rows is ParentChildLink. It has the following fields:
- parent
- id of a part
- child
- id of a part
- quantity
- amount of child
- unit
- unit of the quantity (like kg, m)
- order
- field to order (sort) the BOM
- ctime
- date of creation of the row
- end_time
- date of deletion of the row (null value means no deletion)
The ctime and end_time fields allow openPLM to track changes of a BOM. For example, if the quantity changes, the row is not deleted but, its end_time field is set and a new row is created.
For example, the following BOM (date of creation: 2012/01/01) is
Level | Element | Quantity | Unit | Order |
---|---|---|---|---|
— | P0 | — | – | — |
1 | P1 | 2 | m | 100 |
is stored as one row: parent -> P0, child -> P1, quantity -> 2, unit -> m, order -> 100, ctime -> 2012/01/01, end_time -> null.
If an user changes the unit to km and the quantity to 3, the BOM will be
Level | Element | Quantity | Unit | Order |
---|---|---|---|---|
— | P0 | — | – | — |
1 | P1 | 3 | km | 100 |
and the database will contains two rows (date of modification: 2012/01/02):
- parent -> P0, child -> P1, quantity -> 2, unit -> m, order -> 100, ctime -> 2012/01/01, end_time -> 2012/01/02
- parent -> P0, child -> P1, quantity -> 3, unit -> km, order -> 100, ctime -> 2012/01/02, end_time -> null
If he adds a new child (P2, date of addition: 2012/01/03):
Level | Element | Quantity | Unit | Order |
---|---|---|---|---|
— | P0 | — | – | — |
1 | P1 | 2 | km | 100 |
1 | P2 | 120 | m | 200 |
The database will contains 3 rows:
- parent -> P0, child -> P1, quantity -> 2, unit -> m, order -> 100, ctime -> 2012/01/01, end_time -> 2012/01/02
- parent -> P0, child -> P1, quantity -> 3, unit -> km, order -> 100, ctime -> 2012/01/02, end_time -> null
- parent -> P0, child -> P3, quantity -> 120, unit -> m, order -> 200, ctime -> 2012/01/03, end_time -> null
So previous rows are not altered since they have not been modified.
Some applications need to store additional data on each row. These data should or should not be displayed or editable. For example, an electronic CAD application adds a referenced designator field which tells where a component is put on a board. This field must be displayed and is editable. Another application can add several location fields per row which tell where each child element is located. These location field would be invisible but are useful to generate a STEP document from the BOM.
openPLM has a model named ParentChildLinkExtension to store these extensions. An application can add a model that extends ParentChildLinkExtension and register it. The application only has to override some methods and openPLM will handle the display and edition of the BOM.
PCLE and pcle
Starting from here, PCLE means a model that extends a ParentChildLinkExtension and pcle means an instance of a ParentChildLinkExtension
A PCLE has one mandatory field, link which is a foreign key to a ParentChildLink.
When a ParentChildLink is duplicated (after a modification from an user), its bound extensions are duplicated by calling their ParentChildLinkExtension.clone() method.
By default, a PCLE will not be used by openPLM and must be registered. A PCLE can be registered by calling the registered_pcle() function.
By default, a registered PCLE applies to all BOM. It is possible to change this behaviour by overriding the method ParentChildLinkExtension.apply_to(). This method takes one argument, the parent which will have a child, so it is possible to test its type or some of its attributes.
A PCLE can have as many fields as needed. Added fields are hidden by default. The classmethod ParentChildLinkExtension.get_visible_fields() can be overridden to return a list of visible fields.
The classmethod ParentChildLinkExtension.get_editable_fields() is called to get the list of editable fields that are added to the “add child” form and to the “edit bom” form. By default, it returns the list of visible fields.
Warning
If a PCLE has at least one visible field, openPLM can create at most one pcle per link and it will not be able to display several columns for the same field.
This behaviour is intended to keep the BOM readable and easily editable.
If you really need several visible pcles per link, you can create your own models.Field and play with its formfield() method.
ParentChildLink are never deleted but they are cloned. And since a pcle is bound to a link, it must be clonable. A PCLE must define the ParentChildLinkExtension.clone() method. By default, it raises a NotImplementedError exception.
class ReferenceDesignator(ParentChildLinkExtension):
reference_designator = models.CharField(max_length=200, blank=True)
def __unicode__(self):
return u"ReferenceDesignator<%s>" % self.reference_designator
@classmethod
def get_visible_fields(cls):
return ("reference_designator", )
@classmethod
def apply_to(cls, parent):
# only apply to mother boards
return isinstance(parent, MotherBoard)
def clone(self, link, save, **data):
ref = data.get("reference_designator", self.reference_designator)
clone = ReferenceDesignator(link=link, reference_designator=ref)
if save:
clone.save()
return clone
register(ReferenceDesignator)
register_PCLE(ReferenceDesignator)
Since the version 1.2, it’s possible to compare two BOMs at two different dates and renders a diff view showing differences side by side (like in trac).
The method PartController.cmp_bom() performs the comparison and the view compare_bom() renders the result.
This comparison relies difflib.SequenceMatcher.get_opcodes(). The standard difflib module can compare strings and any types of finite sequences. Using this module is only a matter of input formats. In OpenPLM, BOMs are flatten (see flatten_bom()) and three lines of code do the comparison.