Import images: add file handler
[blender-addons.git] / io_scene_fbx / __init__.py
blob9a7527bc0f0e3d2a114a822c7ff05ba55be5d92f
1 # SPDX-FileCopyrightText: 2011-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 bl_info = {
6 "name": "FBX format",
7 "author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem",
8 "version": (5, 12, 3),
9 "blender": (4, 2, 0),
10 "location": "File > Import-Export",
11 "description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions",
12 "warning": "",
13 "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/scene_fbx.html",
14 "support": 'OFFICIAL',
15 "category": "Import-Export",
19 if "bpy" in locals():
20 import importlib
21 if "import_fbx" in locals():
22 importlib.reload(import_fbx)
23 if "export_fbx_bin" in locals():
24 importlib.reload(export_fbx_bin)
25 if "export_fbx" in locals():
26 importlib.reload(export_fbx)
29 import bpy
30 from bpy.props import (
31 StringProperty,
32 BoolProperty,
33 FloatProperty,
34 EnumProperty,
35 CollectionProperty,
37 from bpy_extras.io_utils import (
38 ImportHelper,
39 ExportHelper,
40 orientation_helper,
41 path_reference_mode,
42 axis_conversion,
43 poll_file_object_drop,
47 @orientation_helper(axis_forward='-Z', axis_up='Y')
48 class ImportFBX(bpy.types.Operator, ImportHelper):
49 """Load a FBX file"""
50 bl_idname = "import_scene.fbx"
51 bl_label = "Import FBX"
52 bl_options = {'UNDO', 'PRESET'}
54 directory: StringProperty()
56 filename_ext = ".fbx"
57 filter_glob: StringProperty(default="*.fbx", options={'HIDDEN'})
59 files: CollectionProperty(
60 name="File Path",
61 type=bpy.types.OperatorFileListElement,
64 ui_tab: EnumProperty(
65 items=(('MAIN', "Main", "Main basic settings"),
66 ('ARMATURE', "Armatures", "Armature-related settings"),
68 name="ui_tab",
69 description="Import options categories",
72 use_manual_orientation: BoolProperty(
73 name="Manual Orientation",
74 description="Specify orientation and scale, instead of using embedded data in FBX file",
75 default=False,
77 global_scale: FloatProperty(
78 name="Scale",
79 min=0.001, max=1000.0,
80 default=1.0,
82 bake_space_transform: BoolProperty(
83 name="Apply Transform",
84 description="Bake space transform into object data, avoids getting unwanted rotations to objects when "
85 "target space is not aligned with Blender's space "
86 "(WARNING! experimental option, use at own risk, known to be broken with armatures/animations)",
87 default=False,
90 use_custom_normals: BoolProperty(
91 name="Custom Normals",
92 description="Import custom normals, if available (otherwise Blender will recompute them)",
93 default=True,
95 colors_type: EnumProperty(
96 name="Vertex Colors",
97 items=(('NONE', "None", "Do not import color attributes"),
98 ('SRGB', "sRGB", "Expect file colors in sRGB color space"),
99 ('LINEAR', "Linear", "Expect file colors in linear color space"),
101 description="Import vertex color attributes",
102 default='SRGB',
105 use_image_search: BoolProperty(
106 name="Image Search",
107 description="Search subdirs for any associated images (WARNING: may be slow)",
108 default=True,
111 use_alpha_decals: BoolProperty(
112 name="Alpha Decals",
113 description="Treat materials with alpha as decals (no shadow casting)",
114 default=False,
116 decal_offset: FloatProperty(
117 name="Decal Offset",
118 description="Displace geometry of alpha meshes",
119 min=0.0, max=1.0,
120 default=0.0,
123 use_anim: BoolProperty(
124 name="Import Animation",
125 description="Import FBX animation",
126 default=True,
128 anim_offset: FloatProperty(
129 name="Animation Offset",
130 description="Offset to apply to animation during import, in frames",
131 default=1.0,
134 use_subsurf: BoolProperty(
135 name="Subdivision Data",
136 description="Import FBX subdivision information as subdivision surface modifiers",
137 default=False,
140 use_custom_props: BoolProperty(
141 name="Custom Properties",
142 description="Import user properties as custom properties",
143 default=True,
145 use_custom_props_enum_as_string: BoolProperty(
146 name="Import Enums As Strings",
147 description="Store enumeration values as strings",
148 default=True,
151 ignore_leaf_bones: BoolProperty(
152 name="Ignore Leaf Bones",
153 description="Ignore the last bone at the end of each chain (used to mark the length of the previous bone)",
154 default=False,
156 force_connect_children: BoolProperty(
157 name="Force Connect Children",
158 description="Force connection of children bones to their parent, even if their computed head/tail "
159 "positions do not match (can be useful with pure-joints-type armatures)",
160 default=False,
162 automatic_bone_orientation: BoolProperty(
163 name="Automatic Bone Orientation",
164 description="Try to align the major bone axis with the bone children",
165 default=False,
167 primary_bone_axis: EnumProperty(
168 name="Primary Bone Axis",
169 items=(('X', "X Axis", ""),
170 ('Y', "Y Axis", ""),
171 ('Z', "Z Axis", ""),
172 ('-X', "-X Axis", ""),
173 ('-Y', "-Y Axis", ""),
174 ('-Z', "-Z Axis", ""),
176 default='Y',
178 secondary_bone_axis: EnumProperty(
179 name="Secondary Bone Axis",
180 items=(('X', "X Axis", ""),
181 ('Y', "Y Axis", ""),
182 ('Z', "Z Axis", ""),
183 ('-X', "-X Axis", ""),
184 ('-Y', "-Y Axis", ""),
185 ('-Z', "-Z Axis", ""),
187 default='X',
190 use_prepost_rot: BoolProperty(
191 name="Use Pre/Post Rotation",
192 description="Use pre/post rotation from FBX transform (you may have to disable that in some cases)",
193 default=True,
196 def draw(self, context):
197 layout = self.layout
198 layout.use_property_split = True
199 layout.use_property_decorate = False # No animation.
201 import_panel_include(layout, self)
202 import_panel_transform(layout, self)
203 import_panel_animation(layout, self)
204 import_panel_armature(layout, self)
206 def execute(self, context):
207 keywords = self.as_keywords(ignore=("filter_glob", "directory", "ui_tab", "filepath", "files"))
209 from . import import_fbx
210 import os
212 if self.files:
213 ret = {'CANCELLED'}
214 dirname = os.path.dirname(self.filepath)
215 for file in self.files:
216 path = os.path.join(dirname, file.name)
217 if import_fbx.load(self, context, filepath=path, **keywords) == {'FINISHED'}:
218 ret = {'FINISHED'}
219 return ret
220 else:
221 return import_fbx.load(self, context, filepath=self.filepath, **keywords)
223 def invoke(self, context, event):
224 return self.invoke_popup(context)
226 def import_panel_include(layout, operator):
227 header, body = layout.panel("FBX_import_include", default_closed=False)
228 header.label(text="Include")
229 if body:
230 body.prop(operator, "use_custom_normals")
231 body.prop(operator, "use_subsurf")
232 body.prop(operator, "use_custom_props")
233 sub = body.row()
234 sub.enabled = operator.use_custom_props
235 sub.prop(operator, "use_custom_props_enum_as_string")
236 body.prop(operator, "use_image_search")
237 body.prop(operator, "colors_type")
240 def import_panel_transform(layout, operator):
241 header, body = layout.panel("FBX_import_transform", default_closed=False)
242 header.label(text="Transform")
243 if body:
244 body.prop(operator, "global_scale")
245 body.prop(operator, "decal_offset")
246 row = body.row()
247 row.prop(operator, "bake_space_transform")
248 row.label(text="", icon='ERROR')
249 body.prop(operator, "use_prepost_rot")
251 import_panel_transform_orientation(body, operator)
254 def import_panel_transform_orientation(layout, operator):
255 header, body = layout.panel("FBX_import_transform_manual_orientation", default_closed=False)
256 header.use_property_split = False
257 header.prop(operator, "use_manual_orientation", text="")
258 header.label(text="Manual Orientation")
259 if body:
260 body.enabled = operator.use_manual_orientation
261 body.prop(operator, "axis_forward")
262 body.prop(operator, "axis_up")
266 def import_panel_animation(layout, operator):
267 header, body = layout.panel("FBX_import_animation", default_closed=True)
268 header.use_property_split = False
269 header.prop(operator, "use_anim", text="")
270 header.label(text="Animation")
271 if body:
272 body.enabled = operator.use_anim
273 body.prop(operator, "anim_offset")
277 def import_panel_armature(layout, operator):
278 header, body = layout.panel("FBX_import_armature", default_closed=True)
279 header.label(text="Armature")
280 if body:
281 body.prop(operator, "ignore_leaf_bones")
282 body.prop(operator, "force_connect_children"),
283 body.prop(operator, "automatic_bone_orientation"),
284 sub = body.column()
285 sub.enabled = not operator.automatic_bone_orientation
286 sub.prop(operator, "primary_bone_axis")
287 sub.prop(operator, "secondary_bone_axis")
290 @orientation_helper(axis_forward='-Z', axis_up='Y')
291 class ExportFBX(bpy.types.Operator, ExportHelper):
292 """Write a FBX file"""
293 bl_idname = "export_scene.fbx"
294 bl_label = "Export FBX"
295 bl_options = {'UNDO', 'PRESET'}
297 filename_ext = ".fbx"
298 filter_glob: StringProperty(default="*.fbx", options={'HIDDEN'})
300 # List of operator properties, the attributes will be assigned
301 # to the class instance from the operator settings before calling.
303 use_selection: BoolProperty(
304 name="Selected Objects",
305 description="Export selected and visible objects only",
306 default=False,
308 use_visible: BoolProperty(
309 name='Visible Objects',
310 description='Export visible objects only',
311 default=False
313 use_active_collection: BoolProperty(
314 name="Active Collection",
315 description="Export only objects from the active collection (and its children)",
316 default=False,
318 global_scale: FloatProperty(
319 name="Scale",
320 description="Scale all data (Some importers do not support scaled armatures!)",
321 min=0.001, max=1000.0,
322 soft_min=0.01, soft_max=1000.0,
323 default=1.0,
325 apply_unit_scale: BoolProperty(
326 name="Apply Unit",
327 description="Take into account current Blender units settings (if unset, raw Blender Units values are used as-is)",
328 default=True,
330 apply_scale_options: EnumProperty(
331 items=(('FBX_SCALE_NONE', "All Local",
332 "Apply custom scaling and units scaling to each object transformation, FBX scale remains at 1.0"),
333 ('FBX_SCALE_UNITS', "FBX Units Scale",
334 "Apply custom scaling to each object transformation, and units scaling to FBX scale"),
335 ('FBX_SCALE_CUSTOM', "FBX Custom Scale",
336 "Apply custom scaling to FBX scale, and units scaling to each object transformation"),
337 ('FBX_SCALE_ALL', "FBX All",
338 "Apply custom scaling and units scaling to FBX scale"),
340 name="Apply Scalings",
341 description="How to apply custom and units scalings in generated FBX file "
342 "(Blender uses FBX scale to detect units on import, "
343 "but many other applications do not handle the same way)",
346 use_space_transform: BoolProperty(
347 name="Use Space Transform",
348 description="Apply global space transform to the object rotations. When disabled "
349 "only the axis space is written to the file and all object transforms are left as-is",
350 default=True,
352 bake_space_transform: BoolProperty(
353 name="Apply Transform",
354 description="Bake space transform into object data, avoids getting unwanted rotations to objects when "
355 "target space is not aligned with Blender's space "
356 "(WARNING! experimental option, use at own risk, known to be broken with armatures/animations)",
357 default=False,
360 object_types: EnumProperty(
361 name="Object Types",
362 options={'ENUM_FLAG'},
363 items=(('EMPTY', "Empty", ""),
364 ('CAMERA', "Camera", ""),
365 ('LIGHT', "Lamp", ""),
366 ('ARMATURE', "Armature", "WARNING: not supported in dupli/group instances"),
367 ('MESH', "Mesh", ""),
368 ('OTHER', "Other", "Other geometry types, like curve, metaball, etc. (converted to meshes)"),
370 description="Which kind of object to export",
371 default={'EMPTY', 'CAMERA', 'LIGHT', 'ARMATURE', 'MESH', 'OTHER'},
374 use_mesh_modifiers: BoolProperty(
375 name="Apply Modifiers",
376 description="Apply modifiers to mesh objects (except Armature ones) - "
377 "WARNING: prevents exporting shape keys",
378 default=True,
380 use_mesh_modifiers_render: BoolProperty(
381 name="Use Modifiers Render Setting",
382 description="Use render settings when applying modifiers to mesh objects (DISABLED in Blender 2.8)",
383 default=True,
385 mesh_smooth_type: EnumProperty(
386 name="Smoothing",
387 items=(('OFF', "Normals Only", "Export only normals instead of writing edge or face smoothing data"),
388 ('FACE', "Face", "Write face smoothing"),
389 ('EDGE', "Edge", "Write edge smoothing"),
391 description="Export smoothing information "
392 "(prefer 'Normals Only' option if your target importer understand split normals)",
393 default='OFF',
395 colors_type: EnumProperty(
396 name="Vertex Colors",
397 items=(('NONE', "None", "Do not export color attributes"),
398 ('SRGB', "sRGB", "Export colors in sRGB color space"),
399 ('LINEAR', "Linear", "Export colors in linear color space"),
401 description="Export vertex color attributes",
402 default='SRGB',
404 prioritize_active_color: BoolProperty(
405 name="Prioritize Active Color",
406 description="Make sure active color will be exported first. Could be important "
407 "since some other software can discard other color attributes besides the first one",
408 default=False,
410 use_subsurf: BoolProperty(
411 name="Export Subdivision Surface",
412 description="Export the last Catmull-Rom subdivision modifier as FBX subdivision "
413 "(does not apply the modifier even if 'Apply Modifiers' is enabled)",
414 default=False,
416 use_mesh_edges: BoolProperty(
417 name="Loose Edges",
418 description="Export loose edges (as two-vertices polygons)",
419 default=False,
421 use_tspace: BoolProperty(
422 name="Tangent Space",
423 description="Add binormal and tangent vectors, together with normal they form the tangent space "
424 "(will only work correctly with tris/quads only meshes!)",
425 default=False,
427 use_triangles: BoolProperty(
428 name="Triangulate Faces",
429 description="Convert all faces to triangles",
430 default=False,
432 use_custom_props: BoolProperty(
433 name="Custom Properties",
434 description="Export custom properties",
435 default=False,
437 add_leaf_bones: BoolProperty(
438 name="Add Leaf Bones",
439 description="Append a final bone to the end of each chain to specify last bone length "
440 "(use this when you intend to edit the armature from exported data)",
441 default=True # False for commit!
443 primary_bone_axis: EnumProperty(
444 name="Primary Bone Axis",
445 items=(('X', "X Axis", ""),
446 ('Y', "Y Axis", ""),
447 ('Z', "Z Axis", ""),
448 ('-X', "-X Axis", ""),
449 ('-Y', "-Y Axis", ""),
450 ('-Z', "-Z Axis", ""),
452 default='Y',
454 secondary_bone_axis: EnumProperty(
455 name="Secondary Bone Axis",
456 items=(('X', "X Axis", ""),
457 ('Y', "Y Axis", ""),
458 ('Z', "Z Axis", ""),
459 ('-X', "-X Axis", ""),
460 ('-Y', "-Y Axis", ""),
461 ('-Z', "-Z Axis", ""),
463 default='X',
465 use_armature_deform_only: BoolProperty(
466 name="Only Deform Bones",
467 description="Only write deforming bones (and non-deforming ones when they have deforming children)",
468 default=False,
470 armature_nodetype: EnumProperty(
471 name="Armature FBXNode Type",
472 items=(('NULL', "Null", "'Null' FBX node, similar to Blender's Empty (default)"),
473 ('ROOT', "Root", "'Root' FBX node, supposed to be the root of chains of bones..."),
474 ('LIMBNODE', "LimbNode", "'LimbNode' FBX node, a regular joint between two bones..."),
476 description="FBX type of node (object) used to represent Blender's armatures "
477 "(use the Null type unless you experience issues with the other app, "
478 "as other choices may not import back perfectly into Blender...)",
479 default='NULL',
481 bake_anim: BoolProperty(
482 name="Baked Animation",
483 description="Export baked keyframe animation",
484 default=True,
486 bake_anim_use_all_bones: BoolProperty(
487 name="Key All Bones",
488 description="Force exporting at least one key of animation for all bones "
489 "(needed with some target applications, like UE4)",
490 default=True,
492 bake_anim_use_nla_strips: BoolProperty(
493 name="NLA Strips",
494 description="Export each non-muted NLA strip as a separated FBX's AnimStack, if any, "
495 "instead of global scene animation",
496 default=True,
498 bake_anim_use_all_actions: BoolProperty(
499 name="All Actions",
500 description="Export each action as a separated FBX's AnimStack, instead of global scene animation "
501 "(note that animated objects will get all actions compatible with them, "
502 "others will get no animation at all)",
503 default=True,
505 bake_anim_force_startend_keying: BoolProperty(
506 name="Force Start/End Keying",
507 description="Always add a keyframe at start and end of actions for animated channels",
508 default=True,
510 bake_anim_step: FloatProperty(
511 name="Sampling Rate",
512 description="How often to evaluate animated values (in frames)",
513 min=0.01, max=100.0,
514 soft_min=0.1, soft_max=10.0,
515 default=1.0,
517 bake_anim_simplify_factor: FloatProperty(
518 name="Simplify",
519 description="How much to simplify baked values (0.0 to disable, the higher the more simplified)",
520 min=0.0, max=100.0, # No simplification to up to 10% of current magnitude tolerance.
521 soft_min=0.0, soft_max=10.0,
522 default=1.0, # default: min slope: 0.005, max frame step: 10.
524 path_mode: path_reference_mode
525 embed_textures: BoolProperty(
526 name="Embed Textures",
527 description="Embed textures in FBX binary file (only for \"Copy\" path mode!)",
528 default=False,
530 batch_mode: EnumProperty(
531 name="Batch Mode",
532 items=(('OFF', "Off", "Active scene to file"),
533 ('SCENE', "Scene", "Each scene as a file"),
534 ('COLLECTION', "Collection",
535 "Each collection (data-block ones) as a file, does not include content of children collections"),
536 ('SCENE_COLLECTION', "Scene Collections",
537 "Each collection (including master, non-data-block ones) of each scene as a file, "
538 "including content from children collections"),
539 ('ACTIVE_SCENE_COLLECTION', "Active Scene Collections",
540 "Each collection (including master, non-data-block one) of the active scene as a file, "
541 "including content from children collections"),
544 use_batch_own_dir: BoolProperty(
545 name="Batch Own Dir",
546 description="Create a dir for each exported file",
547 default=True,
549 use_metadata: BoolProperty(
550 name="Use Metadata",
551 default=True,
552 options={'HIDDEN'},
555 def draw(self, context):
556 layout = self.layout
557 layout.use_property_split = True
558 layout.use_property_decorate = False # No animation.
560 export_main(layout, self)
561 export_panel_include(layout, self)
562 export_panel_transform(layout, self)
563 export_panel_geometry(layout, self)
564 export_panel_armature(layout, self)
565 export_panel_animation(layout, self)
567 @property
568 def check_extension(self):
569 return self.batch_mode == 'OFF'
571 def execute(self, context):
572 from mathutils import Matrix
573 if not self.filepath:
574 raise Exception("filepath not set")
576 global_matrix = (axis_conversion(to_forward=self.axis_forward,
577 to_up=self.axis_up,
578 ).to_4x4()
579 if self.use_space_transform else Matrix())
581 keywords = self.as_keywords(ignore=("check_existing",
582 "filter_glob",
583 "ui_tab",
586 keywords["global_matrix"] = global_matrix
588 from . import export_fbx_bin
589 return export_fbx_bin.save(self, context, **keywords)
592 def export_main(layout, operator):
593 row = layout.row(align=True)
594 row.prop(operator, "path_mode")
595 sub = row.row(align=True)
596 sub.enabled = (operator.path_mode == 'COPY')
597 sub.prop(operator, "embed_textures", text="", icon='PACKAGE' if operator.embed_textures else 'UGLYPACKAGE')
598 row = layout.row(align=True)
599 row.prop(operator, "batch_mode")
600 sub = row.row(align=True)
601 sub.prop(operator, "use_batch_own_dir", text="", icon='NEWFOLDER')
604 def export_panel_include(layout, operator):
605 header, body = layout.panel("FBX_export_include", default_closed=False)
606 header.label(text="Include")
607 if body:
608 sublayout = body.column(heading="Limit to")
609 sublayout.enabled = (operator.batch_mode == 'OFF')
610 sublayout.prop(operator, "use_selection")
611 sublayout.prop(operator, "use_visible")
612 sublayout.prop(operator, "use_active_collection")
614 body.column().prop(operator, "object_types")
615 body.prop(operator, "use_custom_props")
618 def export_panel_transform(layout, operator):
619 header, body = layout.panel("FBX_export_transform", default_closed=False)
620 header.label(text="Transform")
621 if body:
622 body.prop(operator, "global_scale")
623 body.prop(operator, "apply_scale_options")
625 body.prop(operator, "axis_forward")
626 body.prop(operator, "axis_up")
628 body.prop(operator, "apply_unit_scale")
629 body.prop(operator, "use_space_transform")
630 row = body.row()
631 row.prop(operator, "bake_space_transform")
632 row.label(text="", icon='ERROR')
635 def export_panel_geometry(layout, operator):
636 header, body = layout.panel("FBX_export_geometry", default_closed=True)
637 header.label(text="Geometry")
638 if body:
639 body.prop(operator, "mesh_smooth_type")
640 body.prop(operator, "use_subsurf")
641 body.prop(operator, "use_mesh_modifiers")
642 #sub = body.row()
643 #sub.enabled = operator.use_mesh_modifiers and False # disabled in 2.8...
644 #sub.prop(operator, "use_mesh_modifiers_render")
645 body.prop(operator, "use_mesh_edges")
646 body.prop(operator, "use_triangles")
647 sub = body.row()
648 #~ sub.enabled = operator.mesh_smooth_type in {'OFF'}
649 sub.prop(operator, "use_tspace")
650 body.prop(operator, "colors_type")
651 body.prop(operator, "prioritize_active_color")
654 def export_panel_armature(layout, operator):
655 header, body = layout.panel("FBX_export_armature", default_closed=True)
656 header.label(text="Armature")
657 if body:
658 body.prop(operator, "primary_bone_axis")
659 body.prop(operator, "secondary_bone_axis")
660 body.prop(operator, "armature_nodetype")
661 body.prop(operator, "use_armature_deform_only")
662 body.prop(operator, "add_leaf_bones")
665 def export_panel_animation(layout, operator):
666 header, body = layout.panel("FBX_export_bake_animation", default_closed=True)
667 header.use_property_split = False
668 header.prop(operator, "bake_anim", text="")
669 header.label(text="Animation")
670 if body:
671 body.enabled = operator.bake_anim
672 body.prop(operator, "bake_anim_use_all_bones")
673 body.prop(operator, "bake_anim_use_nla_strips")
674 body.prop(operator, "bake_anim_use_all_actions")
675 body.prop(operator, "bake_anim_force_startend_keying")
676 body.prop(operator, "bake_anim_step")
677 body.prop(operator, "bake_anim_simplify_factor")
680 class IO_FH_fbx(bpy.types.FileHandler):
681 bl_idname = "IO_FH_fbx"
682 bl_label = "FBX"
683 bl_import_operator = "import_scene.fbx"
684 bl_file_extensions = ".fbx"
686 @classmethod
687 def poll_drop(cls, context):
688 return poll_file_object_drop(context)
691 def menu_func_import(self, context):
692 self.layout.operator(ImportFBX.bl_idname, text="FBX (.fbx)")
695 def menu_func_export(self, context):
696 self.layout.operator(ExportFBX.bl_idname, text="FBX (.fbx)")
699 classes = (
700 ImportFBX,
701 ExportFBX,
702 IO_FH_fbx,
706 def register():
707 for cls in classes:
708 bpy.utils.register_class(cls)
710 bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
711 bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
714 def unregister():
715 bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
716 bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
718 for cls in classes:
719 bpy.utils.unregister_class(cls)
722 if __name__ == "__main__":
723 register()