From 939ae02fcef74893051f458909a4bc2ebae14820 Mon Sep 17 00:00:00 2001 From: Manuel Lera Ramirez Date: Thu, 11 Apr 2024 17:26:04 +0100 Subject: [PATCH 1/4] WIP --- sbol_utilities/example.py | 51 ++++++++++++++++++++++++ sbol_utilities/sbol3_sbol2_conversion.py | 23 ++++++++--- 2 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 sbol_utilities/example.py diff --git a/sbol_utilities/example.py b/sbol_utilities/example.py new file mode 100644 index 00000000..63eb9526 --- /dev/null +++ b/sbol_utilities/example.py @@ -0,0 +1,51 @@ +import sbol3 +import sbol2 +from sbol_utilities.conversion import convert2to3, convert3to2 +import tyto +from sbol3_sbol2_conversion import SBOL2To3ConversionVisitor, SBOL3To2ConversionVisitor +from pprint import pprint + +sbol3.set_namespace('http://examples.org') + +dummy_doc2 = sbol2.Document() +dummy_doc3 = sbol3.Document() + +component3 = sbol3.Component('component', sbol3.SBO_DNA) +subcomponent3 = sbol3.SubComponent(component3) +component3.features.append(subcomponent3) + +component2 = sbol2.ComponentDefinition('component', sbol3.SBO_DNA) +subcomponent2 = sbol2.Component(definition=component2.identity) +component2.components.add(subcomponent2) + +participation3 = sbol3.Participation(sbol3.SBO_REACTANT, subcomponent3) +participation2 = sbol2.Participation(participant=subcomponent2) +participation2.addRole(sbol2.SBO_REACTANT) + +converter3to2 = SBOL3To2ConversionVisitor(dummy_doc3) +participation2_converted = converter3to2.visit_participation(participation3) +converter2to3 = SBOL2To3ConversionVisitor(dummy_doc2, None) +participation3_converted = converter2to3.visit_participation(participation2) + +print(participation3.participant) +print(participation3_converted.participant) +print(getattr(participation3_converted, 'participant')) + + +def compare_participations(p1, p2): + for k in vars(p1): + if k in ['properties', '_properties']: + continue + if str(getattr(p1, k)) != str(getattr(p2, k)): + print('prop:', k) + print('original:', getattr(p1, k)) + print('converted:', getattr(p2, k)) + print() + + +print('## Participation 2') +compare_participations(participation2, participation2_converted) + +print('## Participation 3') +compare_participations(participation3, participation3_converted) + diff --git a/sbol_utilities/sbol3_sbol2_conversion.py b/sbol_utilities/sbol3_sbol2_conversion.py index ac44446f..5beed377 100644 --- a/sbol_utilities/sbol3_sbol2_conversion.py +++ b/sbol_utilities/sbol3_sbol2_conversion.py @@ -230,9 +230,15 @@ def visit_model(self, a: sbol3.Model): # Priority: 3 raise NotImplementedError('Conversion of Model from SBOL3 to SBOL2 not yet implemented') - def visit_participation(self, a: sbol3.Participation): - # Priority: 2 - raise NotImplementedError('Conversion of Participation from SBOL3 to SBOL2 not yet implemented') + def visit_participation(self, participation3: sbol3.Participation) -> sbol2.Participation: + participation2 = sbol2.Participation( + version=self._sbol2_version(participation3), + participant=participation3.participant + ) + for role in participation3.roles: + participation2.addRole(role) + self._convert_identified(participation3, participation2) + return participation2 def visit_plan(self, a: sbol3.Plan): # Priority: 3 @@ -508,9 +514,14 @@ def visit_module_definition(self, a: sbol2.ModuleDefinition): # Priority: 3 raise NotImplementedError('Conversion of ModuleDefinition from SBOL2 to SBOL3 not yet implemented') - def visit_participation(self, a: sbol2.Participation): - # Priority: 2 - raise NotImplementedError('Conversion of Participation from SBOL2 to SBOL3 not yet implemented') + def visit_participation(self, participation2: sbol2.Participation) -> sbol3.Participation: + participation3 = sbol3.Participation( + roles=participation2.roles, + participant=participation2.participant + ) + self._convert_identified(participation2, participation3) + return participation3 + def visit_plan(self, a: sbol2.Plan): # Priority: 3 From 3a847174735f259371ce17954dd4796f810f7bac Mon Sep 17 00:00:00 2001 From: Manuel Lera-Ramirez Date: Thu, 11 Apr 2024 20:12:51 +0100 Subject: [PATCH 2/4] WIP, on hold for what to do with Interaction.functionalComponents in sbol2 --- sbol_utilities/example.py | 2 +- sbol_utilities/sbol3_sbol2_conversion.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/sbol_utilities/example.py b/sbol_utilities/example.py index 63eb9526..f8e62b4b 100644 --- a/sbol_utilities/example.py +++ b/sbol_utilities/example.py @@ -4,7 +4,7 @@ import tyto from sbol3_sbol2_conversion import SBOL2To3ConversionVisitor, SBOL3To2ConversionVisitor from pprint import pprint - +sbol2.property sbol3.set_namespace('http://examples.org') dummy_doc2 = sbol2.Document() diff --git a/sbol_utilities/sbol3_sbol2_conversion.py b/sbol_utilities/sbol3_sbol2_conversion.py index 5beed377..b17ae6f8 100644 --- a/sbol_utilities/sbol3_sbol2_conversion.py +++ b/sbol_utilities/sbol3_sbol2_conversion.py @@ -210,9 +210,15 @@ def visit_implementation(self, imp3: sbol3.Implementation): # Map over all other TopLevel properties and extensions not covered by the constructor self._convert_toplevel(imp3, imp2) - def visit_interaction(self, a: sbol3.Interaction): - # Priority: 2 - raise NotImplementedError('Conversion of Interaction from SBOL3 to SBOL2 not yet implemented') + def visit_interaction(self, interaction3: sbol3.Interaction) -> sbol2.Interaction: + interaction2 = sbol2.Interaction(version=self._sbol2_version(interaction3), interaction_type=interaction3.types) + for p in interaction3.participations: + interaction2.participations.append(self.visit_participation(p)) + for m in interaction3.measures: + interaction2.measurements.append(self.visit_measure(m)) + self._convert_identified(interaction3, interaction2) + + return interaction2 def visit_interface(self, a: sbol3.Interface): # Priority: 3 From 18f34e8ef39e64ab86582f6a9e0eb2865570e57d Mon Sep 17 00:00:00 2001 From: Manuel Lera Ramirez Date: Fri, 19 Apr 2024 18:36:03 +0200 Subject: [PATCH 3/4] WIP --- sbol_utilities/attempt2.py | 52 ++++++++++++++++++++++ sbol_utilities/attempt2_simplest.py | 28 ++++++++++++ sbol_utilities/attempt3.py | 30 +++++++++++++ sbol_utilities/conversion.py | 2 + sbol_utilities/example.py | 51 ---------------------- sbol_utilities/sbol3_sbol2_conversion.py | 55 +++++++++++++++++------- 6 files changed, 152 insertions(+), 66 deletions(-) create mode 100644 sbol_utilities/attempt2.py create mode 100644 sbol_utilities/attempt2_simplest.py create mode 100644 sbol_utilities/attempt3.py delete mode 100644 sbol_utilities/example.py diff --git a/sbol_utilities/attempt2.py b/sbol_utilities/attempt2.py new file mode 100644 index 00000000..1c5de678 --- /dev/null +++ b/sbol_utilities/attempt2.py @@ -0,0 +1,52 @@ +import sbol2 +import sbol3 +import tyto +from sbol_utilities.conversion import convert2to3, convert3to2 + +sbol3.set_namespace('http://examples.org') + +# sbol2.config.Config.setOption(sbol2.config.ConfigOptions.VALIDATE_ONLINE, False) +doc2 = sbol2.Document() + +component2 = sbol2.ComponentDefinition('hello') + +subcomponent2 = sbol2.FunctionalComponent('subcomp', definition=component2.identity) + +interaction2_component = sbol2.ModuleDefinition('hello2') +interaction2_component.functionalComponents.add(subcomponent2) + +participation2 = sbol2.Participation(uri='reactant1', participant=subcomponent2) +participation2.addRole(sbol2.SBO_REACTANT) + +interaction2 = sbol2.Interaction( + 'reaction1', + interaction_type=tyto.SBO.cleavage +) + +interaction2.participations.add(participation2) + +interaction2_component.interactions.add(interaction2) + + +doc2.add(interaction2_component) +doc2.add(component2) +doc2.write('doc2.ttl') + +doc3 = convert2to3(doc2,['http://examples.org'], use_native_converter=True) + +doc3.write('doc3.ttl') + +doc2_roundtrip = convert3to2(doc3, use_native_converter=True) +doc2_roundtrip.write('doc2_roundrip.ttl') + + + +# def compare_objects(p1, p2): +# for k in vars(p1): +# if k in ['properties', '_properties']: +# continue +# if str(getattr(p1, k)) != str(getattr(p2, k)): +# print('prop:', k) +# print('original:', getattr(p1, k)) +# print('converted:', getattr(p2, k)) +# print() \ No newline at end of file diff --git a/sbol_utilities/attempt2_simplest.py b/sbol_utilities/attempt2_simplest.py new file mode 100644 index 00000000..7f4b8763 --- /dev/null +++ b/sbol_utilities/attempt2_simplest.py @@ -0,0 +1,28 @@ +import sbol2 +import sbol3 +import tyto +from sbol_utilities.conversion import convert2to3, convert3to2 + +sbol3.set_namespace('http://examples.org') + +# sbol2.config.Config.setOption(sbol2.config.ConfigOptions.VALIDATE_ONLINE, False) +doc2 = sbol2.Document() + +component2 = sbol2.ComponentDefinition('hello') + +subcomponent2 = sbol2.FunctionalComponent('subcomp', definition=component2.identity) + +interaction2_component = sbol2.ModuleDefinition('hello2') +interaction2_component.functionalComponents.add(subcomponent2) + +doc2.add(interaction2_component) +doc2.add(component2) +doc2.write('doc2.ttl') + +doc3 = convert2to3(doc2,['http://examples.org'], use_native_converter=True) + +doc3.write('doc3.ttl') + +doc2_roundtrip = convert3to2(doc3, use_native_converter=True) +doc2_roundtrip.write('doc2_roundrip.ttl') + diff --git a/sbol_utilities/attempt3.py b/sbol_utilities/attempt3.py new file mode 100644 index 00000000..a2b0fdef --- /dev/null +++ b/sbol_utilities/attempt3.py @@ -0,0 +1,30 @@ +import sbol3 +import sbol2 +from sbol_utilities.conversion import convert2to3, convert3to2 +from sbol3_sbol2_conversion import SBOL2To3ConversionVisitor, SBOL3To2ConversionVisitor +import tyto + +sbol3.set_namespace('http://examples.org') + +doc3 = sbol3.Document() + +component3 = sbol3.Component('component') +subcomponent3 = sbol3.SubComponent(component3) +component3.features.append(subcomponent3) + + +participation3 = sbol3.Participation(sbol3.SBO_REACTANT, subcomponent3) + +interaction3 = sbol3.Interaction( + tyto.SBO.cleavage, + participations=[participation3] +) + +interaction3_component = sbol3.Component('interaction_component', interactions=[interaction3], types=[tyto.SBO.cleavage]) + +doc3.add(component3) +doc3.add(interaction3_component) + + + + diff --git a/sbol_utilities/conversion.py b/sbol_utilities/conversion.py index 225c55b7..444f4b0e 100644 --- a/sbol_utilities/conversion.py +++ b/sbol_utilities/conversion.py @@ -166,6 +166,8 @@ def change_orientation(o): doc.traverse(change_orientation) report = doc.validate() + print(doc.objects[0], doc.objects[0].types) + print(doc.objects[1], doc.objects[1].types) if len(report): report_string = "\n".join(str(e) for e in doc.validate()) raise ValueError(f'Conversion from SBOL2 to SBOL3 produced an invalid document: {report_string}') diff --git a/sbol_utilities/example.py b/sbol_utilities/example.py deleted file mode 100644 index f8e62b4b..00000000 --- a/sbol_utilities/example.py +++ /dev/null @@ -1,51 +0,0 @@ -import sbol3 -import sbol2 -from sbol_utilities.conversion import convert2to3, convert3to2 -import tyto -from sbol3_sbol2_conversion import SBOL2To3ConversionVisitor, SBOL3To2ConversionVisitor -from pprint import pprint -sbol2.property -sbol3.set_namespace('http://examples.org') - -dummy_doc2 = sbol2.Document() -dummy_doc3 = sbol3.Document() - -component3 = sbol3.Component('component', sbol3.SBO_DNA) -subcomponent3 = sbol3.SubComponent(component3) -component3.features.append(subcomponent3) - -component2 = sbol2.ComponentDefinition('component', sbol3.SBO_DNA) -subcomponent2 = sbol2.Component(definition=component2.identity) -component2.components.add(subcomponent2) - -participation3 = sbol3.Participation(sbol3.SBO_REACTANT, subcomponent3) -participation2 = sbol2.Participation(participant=subcomponent2) -participation2.addRole(sbol2.SBO_REACTANT) - -converter3to2 = SBOL3To2ConversionVisitor(dummy_doc3) -participation2_converted = converter3to2.visit_participation(participation3) -converter2to3 = SBOL2To3ConversionVisitor(dummy_doc2, None) -participation3_converted = converter2to3.visit_participation(participation2) - -print(participation3.participant) -print(participation3_converted.participant) -print(getattr(participation3_converted, 'participant')) - - -def compare_participations(p1, p2): - for k in vars(p1): - if k in ['properties', '_properties']: - continue - if str(getattr(p1, k)) != str(getattr(p2, k)): - print('prop:', k) - print('original:', getattr(p1, k)) - print('converted:', getattr(p2, k)) - print() - - -print('## Participation 2') -compare_participations(participation2, participation2_converted) - -print('## Participation 3') -compare_participations(participation3, participation3_converted) - diff --git a/sbol_utilities/sbol3_sbol2_conversion.py b/sbol_utilities/sbol3_sbol2_conversion.py index b17ae6f8..700b7744 100644 --- a/sbol_utilities/sbol3_sbol2_conversion.py +++ b/sbol_utilities/sbol3_sbol2_conversion.py @@ -150,16 +150,27 @@ def visit_component(self, cp3: sbol3.Component): sbol3.SBO_SIMPLE_CHEMICAL: sbol2.BIOPAX_SMALL_MOLECULE, sbol3.SBO_NON_COVALENT_COMPLEX: sbol2.BIOPAX_COMPLEX} types2 = [type_map.get(t, t) for t in cp3.types] - # Make the Component object and add it to the document - cp2 = sbol2.ComponentDefinition(cp3.identity, types2, version=self._sbol2_version(cp3)) - self.doc2.addComponentDefinition(cp2) + + # Condition to test if it is a ModuleDefinition, probably there are better ways + if cp3.interactions: + # Make the ModuleDefinition object and add it to the document + cp2 = sbol2.ModuleDefinition(cp3.identity, version=self._sbol2_version(cp3)) + self.doc2.addModuleDefinition(cp2) + else: + # Make the Component object and add it to the document + cp2 = sbol2.ComponentDefinition(cp3.identity, types2, version=self._sbol2_version(cp3)) + self.doc2.addComponentDefinition(cp2) + cp2.sequences = cp3.sequences + # Convert the Component properties not covered by the constructor cp2.roles = cp3.roles - cp2.sequences = cp3.sequences + if cp3.features: raise NotImplementedError('Conversion of Component features from SBOL3 to SBOL2 not yet implemented') if cp3.interactions: - raise NotImplementedError('Conversion of Component interactions from SBOL3 to SBOL2 not yet implemented') + for interaction3 in cp3.interactions: + interaction2 = self.visit_interaction(interaction3) + cp2.interactions.add(interaction2) if cp3.constraints: raise NotImplementedError('Conversion of Component constraints from SBOL3 to SBOL2 not yet implemented') if cp3.interface: @@ -211,11 +222,12 @@ def visit_implementation(self, imp3: sbol3.Implementation): self._convert_toplevel(imp3, imp2) def visit_interaction(self, interaction3: sbol3.Interaction) -> sbol2.Interaction: - interaction2 = sbol2.Interaction(version=self._sbol2_version(interaction3), interaction_type=interaction3.types) + interaction2 = sbol2.Interaction(interaction3.identity, version=self._sbol2_version(interaction3), interaction_type=interaction3.types) for p in interaction3.participations: - interaction2.participations.append(self.visit_participation(p)) + part2 = self.visit_participation(p) + interaction2.participations.add(part2) for m in interaction3.measures: - interaction2.measurements.append(self.visit_measure(m)) + interaction2.measurements.add(self.visit_measure(m)) self._convert_identified(interaction3, interaction2) return interaction2 @@ -418,7 +430,7 @@ def visit_component_definition(self, cd2: sbol2.ComponentDefinition): sbol2.BIOPAX_COMPLEX: sbol3.SBO_NON_COVALENT_COMPLEX} types3 = [type_map.get(t, t) for t in cd2.types] # Make the Component object and add it to the document - cp3 = sbol3.Component(cd2.identity, types3, namespace=self._sbol3_namespace(cd2), + cp3 = sbol3.Component(cd2.persistentIdentity, types3, namespace=self._sbol3_namespace(cd2), roles=cd2.roles, sequences=cd2.sequences) self.doc3.add(cp3) # Convert the Component properties not covered by the constructor @@ -496,9 +508,15 @@ def visit_implementation(self, imp2: sbol2.Implementation): # Map over all other TopLevel properties and extensions not covered by the constructor self._convert_toplevel(imp2, imp3) - def visit_interaction(self, a: sbol2.Interaction): - # Priority: 2 - raise NotImplementedError('Conversion of Interaction from SBOL2 to SBOL3 not yet implemented') + def visit_interaction(self, interaction2: sbol2.Interaction) -> sbol3.Interaction: + + interaction3 = sbol3.Interaction( + types=interaction2.types, + participations=[self.visit_participation(p) for p in interaction2.participations], + measures=[self.visit_measure(m) for m in interaction2.measurements] + ) + self._convert_identified(interaction2, interaction3) + return interaction3 def visit_maps_to(self, a: sbol2.mapsto.MapsTo): # Priority: 3 @@ -516,9 +534,16 @@ def visit_module(self, a: sbol2.Module): # Priority: 3 raise NotImplementedError('Conversion of Module from SBOL2 to SBOL3 not yet implemented') - def visit_module_definition(self, a: sbol2.ModuleDefinition): - # Priority: 3 - raise NotImplementedError('Conversion of ModuleDefinition from SBOL2 to SBOL3 not yet implemented') + def visit_module_definition(self, md2: sbol2.ModuleDefinition): + + # Make the Component object and add it to the document + interactions = [self.visit_interaction(i) for i in md2.interactions] + cp3 = sbol3.Component(md2.persistentIdentity, types=sbol3.SBO_FUNCTIONAL_ENTITY, namespace=self._sbol3_namespace(md2), + roles=md2.roles, interactions=interactions) + self.doc3.add(cp3) + # Map over all other TopLevel properties and extensions not covered by the constructor + self._convert_toplevel(md2, cp3) + def visit_participation(self, participation2: sbol2.Participation) -> sbol3.Participation: participation3 = sbol3.Participation( From 8723b1c23790dcf9c2d05f453eb48c46555168e2 Mon Sep 17 00:00:00 2001 From: Manuel Lera Ramirez Date: Mon, 22 Apr 2024 16:21:19 +0200 Subject: [PATCH 4/4] not much progress reached --- sbol_utilities/attempt2.py | 8 +++++--- sbol_utilities/attempt2_simplest.py | 12 ++++++------ sbol_utilities/attempt3_simplest.py | 24 ++++++++++++++++++++++++ sbol_utilities/sbol3_sbol2_conversion.py | 10 +++++++--- 4 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 sbol_utilities/attempt3_simplest.py diff --git a/sbol_utilities/attempt2.py b/sbol_utilities/attempt2.py index 1c5de678..568c6af3 100644 --- a/sbol_utilities/attempt2.py +++ b/sbol_utilities/attempt2.py @@ -10,6 +10,8 @@ component2 = sbol2.ComponentDefinition('hello') +# This is like a subcomponent of a DNA component that would be +# used as parcitipant subcomponent2 = sbol2.FunctionalComponent('subcomp', definition=component2.identity) interaction2_component = sbol2.ModuleDefinition('hello2') @@ -30,14 +32,14 @@ doc2.add(interaction2_component) doc2.add(component2) -doc2.write('doc2.ttl') +doc2.write('doc2.xml') doc3 = convert2to3(doc2,['http://examples.org'], use_native_converter=True) -doc3.write('doc3.ttl') +doc3.write('doc3.xml') doc2_roundtrip = convert3to2(doc3, use_native_converter=True) -doc2_roundtrip.write('doc2_roundrip.ttl') +doc2_roundtrip.write('doc2_roundrip.xml') diff --git a/sbol_utilities/attempt2_simplest.py b/sbol_utilities/attempt2_simplest.py index 7f4b8763..af08b09f 100644 --- a/sbol_utilities/attempt2_simplest.py +++ b/sbol_utilities/attempt2_simplest.py @@ -8,21 +8,21 @@ # sbol2.config.Config.setOption(sbol2.config.ConfigOptions.VALIDATE_ONLINE, False) doc2 = sbol2.Document() -component2 = sbol2.ComponentDefinition('hello') +component2 = sbol2.ComponentDefinition('comp_def') -subcomponent2 = sbol2.FunctionalComponent('subcomp', definition=component2.identity) +subcomponent2 = sbol2.FunctionalComponent('funct_comp', definition=component2.identity) -interaction2_component = sbol2.ModuleDefinition('hello2') +interaction2_component = sbol2.ModuleDefinition('mod_def') interaction2_component.functionalComponents.add(subcomponent2) doc2.add(interaction2_component) doc2.add(component2) -doc2.write('doc2.ttl') +doc2.write('doc2.xml') doc3 = convert2to3(doc2,['http://examples.org'], use_native_converter=True) -doc3.write('doc3.ttl') +doc3.write('doc3.xml') doc2_roundtrip = convert3to2(doc3, use_native_converter=True) -doc2_roundtrip.write('doc2_roundrip.ttl') +doc2_roundtrip.write('doc2_roundrip.xml') diff --git a/sbol_utilities/attempt3_simplest.py b/sbol_utilities/attempt3_simplest.py new file mode 100644 index 00000000..4dc2504e --- /dev/null +++ b/sbol_utilities/attempt3_simplest.py @@ -0,0 +1,24 @@ +import sbol3 +import sbol2 +from sbol_utilities.conversion import convert2to3, convert3to2 +from sbol3_sbol2_conversion import SBOL2To3ConversionVisitor, SBOL3To2ConversionVisitor +import tyto + +sbol3.set_namespace('http://examples.org') + +doc3 = sbol3.Document() + +component3 = sbol3.Component('comp_def', types=[sbol3.SBO_DNA]) +subcomponent3 = sbol3.SubComponent(component3) + +interaction3_component = sbol3.Component('mod_def', types=[sbol3.SBO_BIOCHEMICAL_REACTION]) +interaction3_component.features.append(subcomponent3) + +doc3.add(component3) +doc3.add(interaction3_component) +doc3.write('doc3_simplest.xml') + + + + + diff --git a/sbol_utilities/sbol3_sbol2_conversion.py b/sbol_utilities/sbol3_sbol2_conversion.py index 700b7744..e59052dc 100644 --- a/sbol_utilities/sbol3_sbol2_conversion.py +++ b/sbol_utilities/sbol3_sbol2_conversion.py @@ -166,6 +166,8 @@ def visit_component(self, cp3: sbol3.Component): cp2.roles = cp3.roles if cp3.features: + for feature in cp3.features: + feature2 = self.visit_sub_component(feature) raise NotImplementedError('Conversion of Component features from SBOL3 to SBOL2 not yet implemented') if cp3.interactions: for interaction3 in cp3.interactions: @@ -492,9 +494,9 @@ def visit_experimental_data(self, a: sbol2.ExperimentalData): # Priority: 3 raise NotImplementedError('Conversion of ExperimentalData from SBOL2 to SBOL3 not yet implemented') - def visit_functional_component(self, a: sbol2.FunctionalComponent): - # Priority: 3 - raise NotImplementedError('Conversion of FunctionalComponent from SBOL2 to SBOL3 not yet implemented') + def visit_functional_component(self, fc2: sbol2.FunctionalComponent) -> sbol3.SubComponent: + # TODO: store identity + return sbol3.SubComponent(fc2.definition) def visit_generic_location(self, a: sbol2.GenericLocation): # Priority: 3 @@ -538,9 +540,11 @@ def visit_module_definition(self, md2: sbol2.ModuleDefinition): # Make the Component object and add it to the document interactions = [self.visit_interaction(i) for i in md2.interactions] + functional_components = [self.visit_functional_component(fc) for fc in md2.functionalComponents] cp3 = sbol3.Component(md2.persistentIdentity, types=sbol3.SBO_FUNCTIONAL_ENTITY, namespace=self._sbol3_namespace(md2), roles=md2.roles, interactions=interactions) self.doc3.add(cp3) + cp3.features.extend(functional_components) # Map over all other TopLevel properties and extensions not covered by the constructor self._convert_toplevel(md2, cp3)