Example: Scripting a Table from Primitives
In this exmaple, the task is to script a table component of given width and depth combinations. The table consist of a wood tabletop and a metal frame. It comes in different colours of the tabletop and different colours of the frame. The legs come in two variations, fixed length legs and adjustable leg height. If the legs are adjustable, the user can visualize the adjustment using a range slider.
Definition of the Product
List of possible sizes of the table in milimeters and their article numbers:
1200
700
396.312.127
1200
800
396.312.128
1400
700
396.312.147
1400
800
396.312.148
1600
800
396.312.168
1800
800
396.312.188
1800
900
396.318.189
2000
1000
396.641.891
The tabletop comes in following variations, which add a suffix to the article number divided with a space, dash and space.
oak
ECH
crema white
CRW
nut
NSS
gray
SGR
blue
KBL
And the frame colour adds another suffix:
chrome
731
cream white
121
matte black
JBL
Adjustable legs add yet another suffix ADJ.
Resulting article number example: 396.318.169 - NSS - JBL
Geometry features:
tabletop thickness 40 mm
table frame:
rectangular profile 15x35 mm
distance behind edge of tabletop is 10 mm
legs:
total height 670 mm (incl. base), cylinder diameter 50 mm
mounting base is a bracket of cube shape, 65 x 65 x 12 mm, leg is mounted centered
mounts tightly to the inner corner of the frame
black endcap, 48 x 5 mm
adjustable legs:
telescopic cylindrical shape
same mounting base as standard legs
outer part diameter 60 x 500 mm
inner part diameter 50 x 380 mm
total height in range 630 - 700 mm
same endcaps as the fixed legs have
UX Design:
The user will configure the table in following steps:
size (width first, depth second)
color (tabletop first, frame second)
legs (legs type and eventual table height)
All parameter values will be options with buttons, colors will have thumbnails previews
User can adjust the total height of the table using a parameter "Height settings", that will show if the adjustable legs are selected.
Available materials
Recommended approach
Create the parameters with their values. Choose good parameter keys, suitable parameter types and descriptive labels. You can refer to the Naming Convention for help.
Analyse possible combinations and interactions, implement relevant conditions into whole parameter's visible, visibleInPartList, enabled and value object's condition attributes.
Load the component and check the results. You need to test, if you can get all possible parameters combinations.
Analyse the table of article numbers, create the article number logic.
Design how to setup the geometry, how to group and implement it.
Once ready with everyting, define groups and assign the parameters to them as the last thing (it is good to see all parameters together while testing).
Finally test the component, check all constraints defined in the product definition.
In a real project, you would then submit the component for upload to the database (if you are not allowed to do it yourself) and to the quality check.
Please, take a few moments to try this out. You should be able to achieve this using what you've already learned in the previous chapters.
Solution
1. Creating the parameters
1.1 Look through all the parameters an their values.
width : Decimal = [1200, 1400, 1600, 1800]
depth : Decimal = [700, 800, 900]
materialTabletop : Material = []
materialFrame : Material = []
legType : String = ["fixed", "adjustable"]
totalHeight : Decimal = <670; 740> with step of 1 cm (defined total leg height including the tabletop thickness)
Note: The total height values and the step should be designed by the client. The product's design is their job after all. If such topics are not really clear, don't hesitate to ask in order to prevent feedback loops coming from wrong design understanding.
1.2 Implement in the component source code.
2. Analyse and implement interaction possibilities
2.1 Look through the combinations
widths:
1200, 1400 can be combined with 700, 800 depths
1600 can be combined only with 800 depth
1800 can be combined with 800, 900 depths
depths:
700 can be combined with 1200, 1400 widths
800 can be combined with 1200, 1400, 1600, 1800 widths
900 can be combined only with 1800 width
materials do not have any constraints
leg types also do not have any constraints
totalHeight should not be in the part list (it does not define the product) and is relevant only for adjustable legs -> visible only with relevant legType
2.2 Implement as conditions into the script
3. Load the component and test
If you followed our implementation exacly, you probably discovered an error: In the way we analysed and implemented this, you'll find out, that we can not select the table 200 x 100 cm! If you try this as the default dimensions, you end up dead-locked with:
This is clearly wrong and needs to be fixed. However, clients' usual wishes are that only possible variations are displayed. This is a good UX idea, however as you can see, not always implementable. In such cases, follow top-to-bottom, left-to-right rule of thumb: You select the parameters in a logic that you start at top, limiting possibilities in further parameters and in following groups. It might not always be possible, but once the enduser selects something, it should not modify the configuration that is already behind them.
Therefore, we modify all conditions of width's value objects to true and the depths will adjust accordingly.
4. Article Numbers
When you look into the table, you can see some rules there:
always starts with 396
depths 700 and 800 have constant second part: 312
depth 900 has second part 318
except for 2000x1000, third part are the first digits of the widths and depth
2000x1000 has different second and third part: 641.891
This means, you could:
This works perfectly, however, this is a logical approach that would have been perfectly suitable if you haven't had those exceptions. Also, it is practically impossible for the code review to check it. Also, it will be hard and error prone for you to modify it when adding new size options, making you basically do the analyses once again. As written above, this is not recommended unless you have a lot of parts with perfectly regular logical articleNr codes without exceptions.
Therefore, we implement it using the "brutal force approach":
type #TABLE width, depth and apply formatting
this is the result:
This does not take into account possible combinations, therefore we need to delete them.
Fill in the articleNrs. If you have the data in a formalized way, you can do this really quickly using multicursor approach:
open this md file in VS Code
select the table of articleNrs above, do "place cursor at end of each selected line" (Cmd+Alt+I)
copy the articleNrs
select "if (depth == " and do "place cursor at each of selected occurences" (Cmd+Shift+L)
type articleNr = ' (paste) ';
result:
This approach is not as smart as the first one, but you can:
add or remove articleNrs for new/obsolete parameter combinations really quickly and easily
check the articleNrs in phase of code review, not requiring you to click through every possible combination (you should still check some, though)
We did not take into account the suffixes together with this approach, because the suffixes do not influence what is in front of them. Therefore, we don't do something like #TABLE width, depth, materialTabletop, materialFrame, legType
, because this would deprive the simplicity and take a lot of time to implement and check. Therefore, we use a combination of the "smart" and the "brutal force" approach.
Rest of resulting articleNr code:
5. Design and Implement the Geometry
First, we extract the constants from the product definition and initialize them in a inited block:
See that some of the values are the same (fixedLegDiameter, adjLegSmallDiameter), but we make it as extra variables: First, it is a separate part and therefore the meaning of the constant is not the same. Second: doing it separate makes our geometry script robust in case of "Sorry, we got it wrong, the fixed leg is actually 55, please adjust!" situations.
Another thing we need to realize and prepare for, is to compute the position of the tabletop in the Z axis. This is important, because we need to visualize the adjustable legs correctly and we need to show the tabletop at a constant Z position, if the fixed legs are selected.
Now, we have prepared all necessary values and can display the geometry. We set the pivot of the table on the floor under the center of the table. First, let's draw the tabletop to have it as a reference. The dimensions are width x depth x woodThickness. Because AddCube has pivot in the rear left bottom corner, we need to center it in the top view and move it up:
We continute with the legs. Let's make the fixed leg first. We start with the endcap, then the leg itself above the endcap and we end with the leg's mounting base. We enclose everything in a group, so that everything can be copied at once and moved around. Notice how we call the group, so that it is clear what is in it and where it ends.
Let's see what we have at the moment:
We see the measurements, which are correct - the overall leg height should have been 670, adding woodThickness 40 to resulting 710 height. If possible, do this check as early as possible to find errors early.
Because this leg should be visualized only when the leg type is fixed, we wrap this code in an if-block and prepare the group for the adjustable leg.
We draw the adjustable leg in two phases: lower small part + end cap and above it the big part + mounting base. The lower part is on the ground, but we have to move the upper part based on the tabletopZ value.
Again, notice the calling of the groups. Understanding geometry made with primitives does not force you to assign any identifiers, therefore it is up to you to comment it, so that everyone else understands what is going on in the code.
Either with legType == 'fixed' or else, we've ended with either of the legs in a group. Next step is to make copies and distribute them to the corners of the table. We place following behind the if-else block that draws the legs:
See above what is the legs offset. The frame is 10 mm behind the tabletop edge and 25 mm thick. We have the legs positioned by their cirles' centres, therefore half of the mounting base side has to be added.
We check the positioning of the legs along with the consistence of the totalHeight variable to the dimensions displayed:
Last step is to create the frame. It will be placed under the tabletop, using two AddCube calls and two Copies - that way the cubes always have the same dimensions. We comment out the tabletop and align the frame with the legs. We pay attention that the frame sides do not collide into each other.
With the commented-out tabletop, we can see following:
After uncommenting the tabletop, the table is ready:
See the full sourcecode here
Finalizing
After the component is tested, a merge request should be made. Then, the Production Overseer checks it, accepts the merge request and eventually merges the changes into the master branch in GIT along with updating the live table.
Incoming Change Request or Bug Report
It can happen, that the client is not satisfied with some details. Sometimes, something can be misunderstood or misinterpreted or there are some errors or crashes in the resulting configurator. In that case, we classify this as a bug. If the report goes against the definition, which changes, this is classified as a change request. A bug should be reported in a way, that it describes how IT IS in the current, wrong state and how it SHOULD BE in a correct state. Example:
Compare it with:
The second sentence is okay-ish before it gets changed. This sentence is grammatically incorrect and also does not specify what is the error state. Example: Jack changes the diameter from 100 to 50 mm. After one week, Jane comes back from vacation and because she has received the error report as well, she opens the configurator, where the diameter is 50 mm and changes it back to 100 mm. Of course, this is also a total process failure, but even if the process is well managed, it will cost a lot of mental effort to understand such sentences.
After some time, the client tested and ordered following definition changes from his side. The reason behind it can be a change of the product design or wrong definitions. The message of the client follows:
1200
700
396.312.127
1200
800
396.312.128
1400
700
396.312.147
1400
800
396.312.148
1600
700
396.312.167
1600
800
396.312.168
1600
900
396.318.169
1800
800
396.312.188
1800
900
396.318.189
2000
1000
396.641.891
2200
1000
396.641.221
2400
1000
396.641.241
First, we extract the changes:
1600
700
396.312.167
1600
900
396.318.169
2200
1000
396.641.221
2400
1000
396.641.241
Then, there is the change of the frame if length >= 2000 and a third change, if length >= 2000, add a 5th leg. And there is an error in the part list.
1. Fixing the part list bug
We first fix the part list bug. The client reported, that the part list was not working properly in case of 1600 x 800. The articleNr we get if we try that configuration is: isdt:example_table - CRW - 731 - ADJ
. ArticleNr is inited to the componentId, therefore it seems that we're missing out some if-block in the articleNr computation. After analysing the articleNr code, we can see a wrong depth equation in the list. See the diff that solves the issue:
2. Adding new sizes
We add two valueObjects to width, 2200, 2400.
We modify the depth valueObject's condition.
We finalize articleNrs in the table.
Because the geometry is fully parametrized, it should be as easy as:
3. Changing frame size for big tables
We will not have constants of the frame anymore. We must ensure that each geometry call gets all values that depend on those correctly. Therefore, we have to reorganize the geometry - inited block. If you highlight the frameWidth and frameHeight, you see the legOffset depends on them. No other computation depends on the legOffset.
We move the frameWidth and frameHeight out of the inited block and we do an if-else block depending on the length compared with 2000. After we've got these values, we compute the legs offset:
4. Adding a fifth leg
We will have to make another copy of the leg and move it to the center of the backside. We can copy either the first leg, or the last leg in order to make it easiest:
If we copy the first leg, based on width:
either we move the first leg to rear, copy, move to left
or we move the first leg to right rear
If we copy the last leg, and we are in affected width, we do one more copy and move it to the back center.
Approach 2 is the more simple, therefore we just add:
See the full sourcecode after these changes here
Last updated