Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rosidl_generator_py/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ if(BUILD_TESTING)
# Need to call extras before get_typesupports, to register the extension
rosidl_generator_py_extras(
"${CMAKE_CURRENT_SOURCE_DIR}/bin/rosidl_generator_py"
"${CMAKE_CURRENT_SOURCE_DIR}/rosidl_generator_py/__init__.py"
"${CMAKE_CURRENT_SOURCE_DIR}/rosidl_generator_py/__init__.py;${CMAKE_CURRENT_SOURCE_DIR}/rosidl_generator_py/generate_py_impl.py"
"${CMAKE_CURRENT_SOURCE_DIR}/resource"
)

Expand Down
7 changes: 5 additions & 2 deletions rosidl_generator_py/cmake/register_py.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ macro(rosidl_generator_py_extras BIN GENERATOR_FILES TEMPLATE_DIR)
normalize_path(BIN "${BIN}")
set(rosidl_generator_py_BIN "${BIN}")

normalize_path(GENERATOR_FILES "${GENERATOR_FILES}")
set(rosidl_generator_py_GENERATOR_FILES "${GENERATOR_FILES}")
set(rosidl_generator_py_GENERATOR_FILES "")
foreach(_generator_file ${GENERATOR_FILES})
normalize_path(_generator_file "${_generator_file}")
list(APPEND rosidl_generator_py_GENERATOR_FILES "${_generator_file}")
endforeach()

normalize_path(TEMPLATE_DIR "${TEMPLATE_DIR}")
set(rosidl_generator_py_TEMPLATE_DIR "${TEMPLATE_DIR}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,25 @@ target_include_directories(${_target_name_lib}
${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_py
${PythonExtra_INCLUDE_DIRS}
)
if(APPLE OR WIN32)
# add include directory for numpy headers
set(_python_code
"import numpy"
"print(numpy.get_include())"
)
execute_process(
COMMAND "${PYTHON_EXECUTABLE}" "-c" "${_python_code}"
OUTPUT_VARIABLE _output
RESULT_VARIABLE _result
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT _result EQUAL 0)
message(FATAL_ERROR
"execute_process(${PYTHON_EXECUTABLE} -c '${_python_code}') returned "
"error code ${_result}")
endif()
target_include_directories(${_target_name_lib} PUBLIC "${_output}")
endif()

rosidl_target_interfaces(${_target_name_lib}
${rosidl_generate_interfaces_TARGET} rosidl_typesupport_c)
Expand Down
12 changes: 6 additions & 6 deletions rosidl_generator_py/resource/_action.py.em
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,27 @@ module_name = '_' + convert_camel_case_to_lower_case_underscore(interface_path.s
TEMPLATE(
'_msg.py.em',
package_name=package_name, interface_path=interface_path,
message=action.goal)
message=action.goal, import_statements=import_statements)
TEMPLATE(
'_msg.py.em',
package_name=package_name, interface_path=interface_path,
message=action.result)
message=action.result, import_statements=import_statements)
TEMPLATE(
'_msg.py.em',
package_name=package_name, interface_path=interface_path,
message=action.feedback)
message=action.feedback, import_statements=import_statements)
TEMPLATE(
'_srv.py.em',
package_name=package_name, interface_path=interface_path,
service=action.send_goal_service)
service=action.send_goal_service, import_statements=import_statements)
TEMPLATE(
'_srv.py.em',
package_name=package_name, interface_path=interface_path,
service=action.get_result_service)
service=action.get_result_service, import_statements=import_statements)
TEMPLATE(
'_msg.py.em',
package_name=package_name, interface_path=interface_path,
message=action.feedback_message)
message=action.feedback_message, import_statements=import_statements)
}@


Expand Down
13 changes: 9 additions & 4 deletions rosidl_generator_py/resource/_idl.py.em
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
@# - interface_path (Path relative to the directory named after the package)
@# - content (IdlContent, list of elements, e.g. Messages or Services)
@#######################################################################
@{
import_statements = set()
}@
@
@#######################################################################
@# Handle messages
Expand All @@ -21,8 +24,8 @@ from rosidl_parser.definition import Message
@{
TEMPLATE(
'_msg.py.em',
package_name=package_name, interface_path=interface_path,
message=message)
package_name=package_name, interface_path=interface_path, message=message,
import_statements=import_statements)
}@
@[end for]@
@
Expand All @@ -36,7 +39,8 @@ from rosidl_parser.definition import Service
@{
TEMPLATE(
'_srv.py.em',
package_name=package_name, interface_path=interface_path, service=service)
package_name=package_name, interface_path=interface_path, service=service,
import_statements=import_statements)
}@
@[end for]@
@
Expand All @@ -50,6 +54,7 @@ from rosidl_parser.definition import Action
@{
TEMPLATE(
'_action.py.em',
package_name=package_name, interface_path=interface_path, action=action)
package_name=package_name, interface_path=interface_path, action=action,
import_statements=import_statements)
}@
@[end for]@
102 changes: 97 additions & 5 deletions rosidl_generator_py/resource/_msg.py.em
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from rosidl_cmake import convert_camel_case_to_lower_case_underscore
from rosidl_generator_py.generate_py_impl import constant_value_to_py
from rosidl_generator_py.generate_py_impl import get_python_type
from rosidl_generator_py.generate_py_impl import SPECIAL_NESTED_BASIC_TYPES
from rosidl_generator_py.generate_py_impl import value_to_py
from rosidl_parser.definition import ACTION_FEEDBACK_SUFFIX
from rosidl_parser.definition import ACTION_GOAL_SUFFIX
Expand All @@ -18,6 +19,52 @@ from rosidl_parser.definition import Sequence
from rosidl_parser.definition import String
from rosidl_parser.definition import WString
}@
@#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you consider using a different delimiter here? These look a lot like git conflict markers.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind of delimiter already exists in numerous em templates throughout the various generators. So for this PR I would rather stick with it. But a set of follow up PRs could replace them with whatever else is preferred.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry, I guess I hadn't noticed it before. It really jumped out at me this time. I'm good with it staying, then.

@# Collect necessary import statements for all members
@{
from collections import OrderedDict
import numpy
imports = OrderedDict()
for member in message.structure.members:
if (
isinstance(member.type, NestedType) and
isinstance(member.type.basetype, BasicType) and
member.type.basetype.type in SPECIAL_NESTED_BASIC_TYPES
):
if isinstance(member.type, Array):
member_names = imports.setdefault(
'import numpy', [])
elif isinstance(member.type, Sequence):
member_names = imports.setdefault(
'import array', [])
else:
assert False
member_names.append(member.name)
}@
@#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@
@#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
@[if imports]@


# Import statements for member types
@[ for import_statement, member_names in sorted(imports.items())]@

@[ for member_name in member_names]@
# Member '@(member_name)'
@[ end for]@
@[ if import_statement in import_statements]@
# already imported above
# @
@[ end if]@
@(import_statement)@
@[ if import_statement not in import_statements]@
@{import_statements.add(import_statement)}@
# noqa: E402@
@[ end if]
@[ end for]@
@[end if]@
@#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


class Metaclass_@(message.structure.type.name)(type):
Expand Down Expand Up @@ -212,13 +259,25 @@ if isinstance(type_, NestedType):
[chr(0) for x in range(@(member.type.size))]
)
@[ else]@
@[ if isinstance(member.type.basetype, BasicType) and member.type.basetype.type in SPECIAL_NESTED_BASIC_TYPES]@
if '@(member.name)' not in kwargs:
self.@(member.name) = numpy.zeros(@(member.type.size), dtype=@(SPECIAL_NESTED_BASIC_TYPES[member.type.basetype.type]['dtype']))
else:
self.@(member.name) = numpy.array(kwargs.get('@(member.name)'), dtype=@(SPECIAL_NESTED_BASIC_TYPES[member.type.basetype.type]['dtype']))
assert self.@(member.name).shape == (@(member.type.size), )
@[ else]@
self.@(member.name) = kwargs.get(
'@(member.name)',
[@(get_python_type(type_))() for x in range(@(member.type.size))]
)
@[ end if]@
@[ end if]@
@[ elif isinstance(member.type, Sequence)]@
@[ if isinstance(member.type.basetype, BasicType) and member.type.basetype.type in SPECIAL_NESTED_BASIC_TYPES]@
self.@(member.name) = array.array('@(SPECIAL_NESTED_BASIC_TYPES[member.type.basetype.type]['type_code'])', kwargs.get('@(member.name)', []))
@[ else]@
self.@(member.name) = kwargs.get('@(member.name)', [])
@[ end if]@
@[ elif isinstance(type_, BasicType) and type_.type == 'octet']@
self.@(member.name) = kwargs.get('@(member.name)', bytes([0]))
@[ elif isinstance(type_, BasicType) and type_.type in ('char', 'wchar')]@
Expand All @@ -240,7 +299,11 @@ if isinstance(type_, NestedType):
if not isinstance(other, self.__class__):
return False
@[for member in message.structure.members]@
@[ if isinstance(member.type, Array) and isinstance(member.type.basetype, BasicType) and member.type.basetype.type in SPECIAL_NESTED_BASIC_TYPES]@
if all(self.@(member.name) != other.@(member.name)):
@[ else]@
if self.@(member.name) != other.@(member.name):
@[ end if]@
return False
@[end for]@
return True
Expand Down Expand Up @@ -269,6 +332,27 @@ if member.name in dict(inspect.getmembers(builtins)).keys():

@@@(member.name).setter@(noqa_string)
def @(member.name)(self, value):
@[ if isinstance(member.type, NestedType) and isinstance(member.type.basetype, BasicType) and member.type.basetype.type in SPECIAL_NESTED_BASIC_TYPES]@
@[ if isinstance(member.type, Array)]@
if isinstance(value, numpy.ndarray):
assert value.dtype == @(SPECIAL_NESTED_BASIC_TYPES[member.type.basetype.type]['dtype']), \
"The '@(member.name)' numpy.ndarray() must have the dtype of '@(SPECIAL_NESTED_BASIC_TYPES[member.type.basetype.type]['dtype'])'"
assert value.size == @(member.type.size), \
"The '@(member.name)' numpy.ndarray() must have a size of @(member.type.size)"
self._@(member.name) = value
return
@[ elif isinstance(member.type, Sequence)]@
if isinstance(value, array.array):
assert value.typecode == '@(SPECIAL_NESTED_BASIC_TYPES[member.type.basetype.type]['type_code'])', \
"The '@(member.name)' array.array() must have the type code of '@(SPECIAL_NESTED_BASIC_TYPES[member.type.basetype.type]['type_code'])'"
@[ if isinstance(member.type, BoundedSequence)]@
assert len(value) <= @(member.type.upper_bound), \
"The '@(member.name)' array.array() must have a size <= @(member.type.upper_bound)"
@[ end if]@
self._@(member.name) = value
return
@[ end if]@
@[ end if]@
if __debug__:
@[ if isinstance(type_, NamespacedType)]@
@[ if (
Expand Down Expand Up @@ -348,15 +432,15 @@ bound = 2**nbits
@[ elif isinstance(type_, BasicType) and type_.type == 'octet']@
((isinstance(value, bytes) or isinstance(value, ByteString)) and
len(value) == 1), \
"The '@(member.name)' field must of type 'bytes' or 'ByteString' with a length 1"
"The '@(member.name)' field must be of type 'bytes' or 'ByteString' with length 1"
@[ elif isinstance(type_, BasicType) and type_.type == 'char']@
((isinstance(value, str) or isinstance(value, UserString)) and
len(value) == 1 and ord(value) >= -128 and ord(value) < 128), \
"The '@(member.name)' field must of type 'str' or 'UserString' " \
'with a length 1 and the character ord() in [-128, 127]'
"The '@(member.name)' field must be of type 'str' or 'UserString' " \
'with length 1 and the character ord() in [-128, 127]'
@[ elif isinstance(type_, BaseString)]@
isinstance(value, str), \
"The '@(member.name)' field must of type '@(get_python_type(type_))'"
"The '@(member.name)' field must be of type '@(get_python_type(type_))'"
@[ elif isinstance(type_, BasicType) and type_.type in [
'boolean',
'float', 'double',
Expand All @@ -366,7 +450,7 @@ bound = 2**nbits
'int64', 'uint64',
]]@
isinstance(value, @(get_python_type(type_))), \
"The '@(member.name)' field must of type '@(get_python_type(type_))'"
"The '@(member.name)' field must be of type '@(get_python_type(type_))'"
@[ if type_.type.startswith('int')]@
@{
nbits = int(type_.type[3:])
Expand All @@ -385,5 +469,13 @@ bound = 2**nbits
@[ else]@
False
@[ end if]@
@[ if isinstance(member.type, NestedType) and isinstance(member.type.basetype, BasicType) and member.type.basetype.type in SPECIAL_NESTED_BASIC_TYPES]@
@[ if isinstance(member.type, Array)]@
self._@(member.name) = numpy.array(value, dtype=@(SPECIAL_NESTED_BASIC_TYPES[member.type.basetype.type]['dtype']))
@[ elif isinstance(member.type, Sequence)]@
self._@(member.name) = array.array('@(SPECIAL_NESTED_BASIC_TYPES[member.type.basetype.type]['type_code'])', value)
@[ end if]@
@[ else]@
self._@(member.name) = value
@[ end if]@
@[end for]@
Loading