Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Subclassing OpenSim classes in Python #710

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
34 changes: 34 additions & 0 deletions Bindings/Python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,37 @@
geometry_path = os.path.join(curFolder, 'Geometry')
if os.path.exists(geometry_path):
ModelVisualizer.addDirToGeometrySearchPaths(geometry_path)

import copy

# TODO consider moving this to the interface files.
def declare_concrete_object(original_class):
"""This method should be used as a decorator to achieve the similar
behavior as the C++ OpenSim_DECALRE_CONCRETE_OBJECT macro.

"""

# Add abstract methods.
def getConcreteClassName(self):
return original_class.__name__
def clone(self):
# TODO this implementation is incorrect!
print "DEBUG", type(original_class().__disown__()), "ENDDEBUG"
#print "DEBUG", type(copy.deepcopy(self)), "ENDDEBUG"
#return None
#return copy.deepcopy(self) #None
obj = self.__class__()
obj._markAdopted()
#return obj #original_class().__disown__()
return original_class().__disown__()

original_class.getConcreteClassName = getConcreteClassName
original_class.clone = clone

# TODO getClassName()
# TODO safeDownCast()
# TODO Self
# TODO Super

return original_class

9 changes: 9 additions & 0 deletions Bindings/Python/swig/python_common.i
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ using namespace SimTK;
// }
//}

// Directors
// =========
%feature("director") OpenSim::Object::clone;
%feature("director") OpenSim::Object::getConcreteClassName;
%feature("director") OpenSim::Component;

// Rename
// ======

%rename(printToXML) OpenSim::Object::print(const std::string&) const;
%rename(printToXML) OpenSim::XMLDocument::print(const std::string&);
%rename(printToXML) OpenSim::XMLDocument::print();
Expand Down
10 changes: 10 additions & 0 deletions Bindings/Python/swig/python_simulation.i
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ using namespace SimTK;
}
}

// Directors
// =========
%feature("director") OpenSim::Analysis;
// TODO quick way to allow printing to XML.
%feature("nodirector") OpenSim::Analysis::updateXMLNode;
%feature("director") OpenSim::ModelComponent;
%feature("nodirector") OpenSim::ModelComponent::updateXMLNode;
// This is a `final` method, and SWIG doesn't know to avoid them for directors.
%feature("nodirector") OpenSim::ModelComponent::extendConnect;

// Rename
// ======

Expand Down
161 changes: 161 additions & 0 deletions Bindings/Python/tests/test_extending_classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
"""Subclassing OpenSim classes in python Using SWIG director classes.

"""

import os
import unittest

import opensim as osim
import copy

test_dir = os.path.join(os.path.dirname(os.path.abspath(osim.__file__)),
'tests')

class MyAnalysis(osim.Analysis):
def begin(self, state):
print("BEGIN!!!!")
self.test_begin = 61
return 1
def step(self, state, stepNumber):
print("STEP!!!!")
self.test_step = 51
return 1
def end(self, state):
print("END!!!!")
self.test_end = 37
return 1
def clone(self):
# this's wrong sinc no cloning is done but seems unrelated to SWIG wiring
print("CLONING!")
return self
def getConcreteClassName(self):
print("in getConcreteClassName")
return "MyAnalysis"


@osim.declare_concrete_object
class MyModelComponent(osim.ModelComponent):
def __init__(self):
super(MyModelComponent, self).__init__()
def extendAddToSystem(self, *args, **kwargs):
print("DEBUG!!!!")
self.addStateVariable("funstate")
print("DEBUG????")

class TestExtendingClasses(unittest.TestCase):
def test_analysis_forward_adopt(self):
# Adopt the analysis and run the ForwardTool.
model = osim.Model(os.path.join(test_dir, "arm26.osim"))
state = model.initSystem()
myanalysis = MyAnalysis()
myanalysis.setModel(model)
myanalysis.setName('my_analysis')

# Add our python subclass to the model.
model.getAnalysisSet().adoptAndAppend(myanalysis)

# Simple tests.
analysis = model.getAnalysisSet().get(0)
assert analysis.getConcreteClassName() == 'MyAnalysis'
analysis.begin(state)

# Run tool.
forward = osim.ForwardTool()
forward.setModel(model)
forward.run()
print("TOOL RAN SUCCESSFULLY!!!!")
# Make sure that MyAnalysis was evaluated.
#assert analysis.test_begin == 61
#assert analysis.test_step == 51
#assert analysis.test_end == 37 # TODO fails.

def test_analysis_clone(self):
# Adopt the analysis and run the ForwardTool.
model = osim.Model(os.path.join(test_dir, "arm26.osim"))
myanalysis = MyAnalysis()
myanalysis.setName('my_analysis')

#import copy
#copy.deepcopy(myanalysis)

# Add our python subclass to the model.
model.getAnalysisSet().cloneAndAppend(myanalysis)

# Simple tests.
#analysis = model.getAnalysisSet().get(0)

def test_analysis_concreteclassname(self):
# Adopt the analysis and run the ForwardTool.
model = osim.Model(os.path.join(test_dir, "arm26.osim"))
state = model.initSystem()
myanalysis = MyAnalysis()
myanalysis.setName('my_analysis')

# Add our python subclass to the model.
model.getAnalysisSet().adoptAndAppend(myanalysis)

# Simple tests.
analysis = model.getAnalysisSet().get(0)
print analysis.getConcreteClassName()
assert analysis.getConcreteClassName() == 'MyAnalysis'

def test_analysis_forward_clone(self):
# Adopt the analysis and run the ForwardTool.
model = osim.Model(os.path.join(test_dir, "arm26.osim"))
state = model.initSystem()
myanalysis = MyAnalysis()
myanalysis.setName('my_analysis')
myanalysis.setModel(model);
# Add our python subclass to the model.
model.getAnalysisSet().cloneAndAppend(myanalysis)

# Simple tests.
analysis = model.getAnalysisSet().get(0)
assert analysis.getConcreteClassName() == 'MyAnalysis'
analysis.begin(state)

# Run tool.
forward = osim.ForwardTool()
forward.setModel(model)
forward.run()

# Make sure that MyAnalysis was evaluated.
assert analysis.test_begin == 61
assert analysis.test_step == 51
#assert analysis.test_end == 37 # TODO fails.

def test_registerType(self):
# TODO causes segfault.
ma = MyAnalysis()
ma.thisown = False
osim.OpenSimObject.registerType(ma) #.__disown__())
a = osim.OpenSimObject.getDefaultInstanceOfType('MyAnalysis')
assert a.getConcreteClassName() == 'MyAnalysis'

def test_printToXML(self):
ma = MyAnalysis()
ma.printToXML('test_MyAnalysis.xml')

def test_ModelComponent_addToSystem(self):
# Adopt the analysis and run the ForwardTool.
model = osim.Model(os.path.join(test_dir, "arm26.osim"))
mc = MyModelComponent()
model.addModelComponent(mc)
mc.extendAddToSystem()
state = model.initSystem()

def test_ModelComponent(self):
# Adopt the analysis and run the ForwardTool.
model = osim.Model(os.path.join(test_dir, "arm26.osim"))
mc = MyModelComponent()
model.addModelComponent(mc)
state = model.initSystem()

# Run tool.
forward = osim.ForwardTool()
forward.setModel(model)
forward.run()




Loading