Surface Finish Plugin Updates

Also see :


Introduction

In SDS2 2023, new features for material finishes were introduced. To fully take advantage of these features and provide a consistent user experience, plugins should be updated to employ new finish idioms and use new finish widgets that support these idioms.

New finish idioms and behaviors

Formerly, the value of a finish field was one of a fixed set of strings like “Red Oxide” or “Yellow Zinc”. Now finish fields are integers that index into the user-modifiable finish table in setup.

Members now have a primary finish, and may have secondary finishes. On member edit windows, a “Reset” button is placed next to the primary finish. When Reset is checked, process will change the finish of all the material on the member to “Auto”, forcing the material to use whatever finish is specified by the member or component that created the material, rather than whatever finish a user may have manually set on the material via material edit. When a user has changed the finish on a material created by the member, an indicator will appear next to the member’s Reset checkbutton with a tooltip that states “Submaterials have user finishes”.

Secondary finish fields on members such as Stair or HandRail as well as finish fields on material will have an “Auto” checkbox next to the finish combobox. When Auto is checked on member secondary finishes, the finish field will use whatever is specified for the member’s primary finish. When Auto is checked on a finish field in a material edit dialog, it will use whatever finish is specified by the member when the member is reprocessed.

Members that add material to a different member should use the default finish on that other member when adding material to that other member. Secondary finishes on members have formerly been used to set color and provide the aesthetically pleasing contrast between different parts of a member. Use of secondary finishes for this purpose is deprecated. If parts on a member typically all use the same finish, consider removing the unnecessary secondary finishes.

On all edit windows, the finish combobox should be placed directly beneath the grade combobox.

Members should no longer use the galvanized attribute or have a galvanized checkbox in the member edit dialog. The finish attribute is used instead.

Updating plugins with the new finish idioms

Update or add primary finish widget on Member Edit dialog

If there is an existing finish widget that specifies the primary finish to be used on a member, delete that widget. Add or replace the finish widget with this:

from dialog.choose_file import ForceChooseFinish
...
primary_finish_widget = ForceChooseFinish(parent_widget)

No other arguments to ForceChooseFinish() should be necessary.

The finish widget should be placed directly underneath the grade widget. The galvanized widget should be removed.

Update member material creation code to use primary finish

Materials that are created with a hard-coded finish or use the old primaryfinish now need to use the new primary finish. To do this, assign Member-Base.default_finish_id to the material finish attribute.

For example, this:

pl = RectPlate()
pl.SurfaceFinish = self.my_old_primary_finish

or this:

pl = RectPlate()
pl.SurfaceFinish = 'Red Oxide'

or this:

pl = RectPlate()
# No finish attribute assignment

The following should be changed for a plate created in a custom member method:

pl = RectPlate()
pl.SurfaceFinish = self.default_finish_id

Remove or replace any code that uses the galvanized attribute.

Update member secondary finish widgets

from dialog.choose_file import ForceChooseFinish, AutoChooseFinish
...
primary_finish_widget = ForceChooseFinish(parent_widget)
...
myfinishfield_widget = AutoChooseFinish(parent_widget, auto_name='auto_myfinishfield', combobox_name='myfinishfield')

#Add rules to update the secondary finish widget when #it is set to auto or the primary finish is changed.
def enforce_auto_finish(auto_finish_widget, primary_finish_widget): if auto_finish_widget.checkbox.Get(): auto_finish_widget.Set(primary_finish_widget.Get()) myfinishfield_widget.checkbox.AddRule(Always, lambda _: enforce_auto_finish(myfinishfield_widget, primary_finish_widget) primary_finish_widget.AddRule(Modified, lambda _: enforce_auto_finish(myfinishfield_widget, primary_finish_widget)

Update other finish widgets

Non-primary finish widgets include any finish fields on dialogs for custom components, material, setup, and tools.

Note the field name used by the finish widget you are replacing (hereafter “myfinishfield”), delete that widget, and replace it thusly:

 from dialog.choose_file import AutoChooseFinish
...
myfinishfield_widget = AutoChooseFinish(parent_widget, auto_name='auto_myfinishfield', combobox_name='myfinishfield') 

Update secondary finish material other material creation code

pl = RectPlate()
if self.auto_myfinishfield:
	pl.SurfaceFinish = self.default_finish_id
else:
	pl.SurfaceFinish = self.myfinishfield

Update initialization, serialization, and convert

Classes that use StateAccessor or Metamorphoses for serialization (and sometimes classes that use other serialization schemes) have “versions” which are lists of attribute names with their default values. Create a new version where the finish fields have integer default values rather than string default values. Also, add any new “auto_myfinishfield” attributes to the new version.

If your class provides an __init__() method, make sure that any finish fields are initialized to integers rather than strings and new auto fields are initialized to True. If your class uses StateAccessor or Metamorphoses for serialization, the __init__() method is typically generated for you using the current serialization version, and adding a new version will automatically update your __init__().

Since objects (members, components, setup, etc) in existing jobs were serialized with string finishes, they need to be converted to the most recent serialization version that uses integer finishes. If they are not converted properly, errors will occur when finish attributes are accessed.

Also, auto finish attributes (e.g., auto_myfinishfield) should be set to False by convert so that finishes on existing objects are not changed unexpectedly.

The way a convert is implemented is dependent on the serialization scheme (StateAccessor, Metamorphoses, etc). For classes that do not use StateAccessor or Metamorphoses, converts are ad hoc. You must store a version number in your object and add code to update the version number and do other convert tasks in __setstate__(). For StateAccessor, you must create or update the __convert__() method on your class. For Metamorphoses, you must create or update the convert() method on your Metamorphoses-derived class.

Here is an example of a Metamorphoses convert method:

import job
...
@classmethod
def convert(cls, obj, old_version_id, new_version_id):
	updated_finishes_version_id = 3
	
	if old_version_id < updated_finishes_version_id:
	
		# Initially set the primary finish to the default job finish.
		obj.default_finish_id = Job().surface_finish
	
		# Find string finishes in setup finish table.
		finish_names = [finish.display_name.lower() for finish in 
				job.Job().surface_finishes()]
		finish_values = range(len(finish_names))
	
		for name, value in zip(finish_names, finish_values):
			if obj.old_primary_finish.lower() == name:
				obj.default_finish_id = value
				del obj.old_primary_finish
				break
	
		for name, value in zip(finish_names, finish_values):
			if obj.myfinishfield.lower() == name:
				obj.myfinishfield = value
				break
	
		# If the old finish field was not found in the setup finish table,
		# set it to the job default finish.
		if type(obj.myfinishfield) == str:
				obj.myfinishfield = job.Job().surface_finish
					
		# Don't change secondary finishes
		obj.myfinishfield_auto = False
		
	Here is an example of a StateAccessor convert method:
	
	def __convert__(self, old_version_id):
		updated_finishes_version_id = 3
	
		if old_version_id < updated_finishes_version_id:
		
		# Set the primary finish to the default job finish.
		self.default_finish_id = Job().surface_finish
		
		# Find string finishes in setup finish table.
		finish_names = [finish.display_name.lower() for finish in 
				job.Job().surface_finishes()]
		finish_values = range(len(finish_names))
		
		for name, value in zip(finish_names, finish_values):
			if self.old_primary_finish.lower() == name:
				self.default_finish_id = value
				del self.old_primary_finish
				break
		
		for name, value in zip(finish_names, finish_values):
			if self.myfinishfield.lower() == name:
				self.myfinishfield = value
				break
			
		# If the old finish field was not found in the setup finish table,
		# set it to the job default finish.
		if type(self.myfinishfield) == str:
			self.myfinishfield = job.Job().surface_finish
		
		# Don't change secondary finishes
		self.myfinishfield_auto = False	

Setup

If there are custom setup classes for your plugin, remember to fix gui and serialization issues for those in similar fashion.