Skip to content
Open
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
Binary file added Resources/Icons/MjActuator.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjBallJoint.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjBody.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjBox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjCamera.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjContactExclude.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjContactPair.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjCylinder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjDefault.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjEquality.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjFreeJoint.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjGeom.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjHingeJoint.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjInertial.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjJoint.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjKeyframe.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjMeshGeom.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjSensor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjSite.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjSlideJoint.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjSphere.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjTendon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Icons/MjWorldBody.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions Source/URLab/Private/MuJoCo/Components/Actuators/MjActuator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
#include "MuJoCo/Core/MjPhysicsEngine.h"
#include "MuJoCo/Core/MjArticulation.h"
#include "MuJoCo/Core/Spec/MjSpecWrapper.h"
#include "MuJoCo/Components/Joints/MjJoint.h"
#include "MuJoCo/Components/Geometry/MjSite.h"
#include "MuJoCo/Components/Bodies/MjBody.h"
#include "MuJoCo/Components/Tendons/MjTendon.h"
#include "Utils/URLabLogging.h"

UMjActuator::UMjActuator()
Expand Down Expand Up @@ -346,3 +350,46 @@ void UMjActuator::RegisterToSpec(FMujocoSpecWrapper& Wrapper, mjsBody* ParentBod
ExportTo(act, effectiveDefault);
}

#if WITH_EDITOR
TArray<FString> UMjActuator::GetTargetNameOptions() const
{
UClass* FilterClass = nullptr;
switch (TransmissionType)
{
case EMjActuatorTrnType::Joint:
case EMjActuatorTrnType::JointInParent:
FilterClass = UMjJoint::StaticClass();
break;
case EMjActuatorTrnType::Tendon:
FilterClass = UMjTendon::StaticClass();
break;
case EMjActuatorTrnType::Site:
case EMjActuatorTrnType::SliderCrank:
FilterClass = UMjSite::StaticClass();
break;
case EMjActuatorTrnType::Body:
FilterClass = UMjBody::StaticClass();
break;
default:
FilterClass = UMjJoint::StaticClass();
break;
}
return GetSiblingComponentOptions(this, FilterClass);
}

TArray<FString> UMjActuator::GetSliderSiteOptions() const
{
return GetSiblingComponentOptions(this, UMjSite::StaticClass());
}

TArray<FString> UMjActuator::GetRefSiteOptions() const
{
return GetSiblingComponentOptions(this, UMjSite::StaticClass());
}

TArray<FString> UMjActuator::GetDefaultClassOptions() const
{
return GetSiblingComponentOptions(this, UMjDefault::StaticClass(), true);
}
#endif

7 changes: 7 additions & 0 deletions Source/URLab/Private/MuJoCo/Components/Bodies/MjBody.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -448,3 +448,10 @@ void UMjBody::RegisterToSpec(FMujocoSpecWrapper& Wrapper, mjsBody* ParentBody)
Setup(GetAttachParent(), ParentBody, &Wrapper);
}
}

#if WITH_EDITOR
TArray<FString> UMjBody::GetChildClassOptions() const
{
return UMjComponent::GetSiblingComponentOptions(this, UMjDefault::StaticClass(), true);
}
#endif
25 changes: 25 additions & 0 deletions Source/URLab/Private/MuJoCo/Components/Constraints/MjEquality.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
#include "MuJoCo/Utils/MjUtils.h"
#include "XmlNode.h"
#include "MuJoCo/Utils/MjXmlUtils.h"
#include "MuJoCo/Components/Bodies/MjBody.h"
#include "MuJoCo/Components/Joints/MjJoint.h"
#include "MuJoCo/Components/Tendons/MjTendon.h"
#include "Utils/URLabLogging.h"

UMjEquality::UMjEquality()
Expand Down Expand Up @@ -212,3 +215,25 @@ void UMjEquality::RegisterToSpec(FMujocoSpecWrapper& Wrapper, mjsBody* ParentBod

ExportTo(Eq);
}

#if WITH_EDITOR
TArray<FString> UMjEquality::GetObjOptions() const
{
UClass* FilterClass = nullptr;
switch (EqualityType)
{
case EMjEqualityType::Connect:
case EMjEqualityType::Weld:
FilterClass = UMjBody::StaticClass();
break;
case EMjEqualityType::Joint:
FilterClass = UMjJoint::StaticClass();
break;
case EMjEqualityType::Tendon:
FilterClass = UMjTendon::StaticClass();
break;
}
if (!FilterClass) return {TEXT("")};
return UMjComponent::GetSiblingComponentOptions(this, FilterClass);
}
#endif
16 changes: 16 additions & 0 deletions Source/URLab/Private/MuJoCo/Components/Defaults/MjDefault.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ void UMjDefault::ImportFromXml(const FXmlNode* Node)
else ClassName = TEXT("main");
}

#if WITH_EDITOR
void UMjDefault::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);

// If ClassName is empty after any edit, auto-populate from component name
if (ClassName.IsEmpty())
{
ClassName = GetName();
ClassName.ReplaceInline(TEXT("_GEN_VARIABLE"), TEXT(""));
}
}
#endif



void UMjDefault::ExportTo(mjsDefault* def)
{
if (!def) return;
Expand Down
32 changes: 32 additions & 0 deletions Source/URLab/Private/MuJoCo/Components/Geometry/MjGeom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
// CoACD (MIT), and libzmq (MPL 2.0). See ThirdPartyNotices.txt for details.

#include "MuJoCo/Components/Geometry/MjGeom.h"
#include "Components/StaticMeshComponent.h"
#include "MuJoCo/Utils/MjXmlUtils.h"
#include "MuJoCo/Utils/MjUtils.h"
#include "MuJoCo/Utils/MjOrientationUtils.h"
Expand Down Expand Up @@ -508,9 +509,33 @@ void UMjGeom::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent
UE_LOG(LogURLab, Log, TEXT("[MjGeom] User manually changed Scale for '%s'. Marking bOverride_Size = true."), *GetName());
}
}

// Sync MjClassName when DefaultClass changes
if (PropertyName == GET_MEMBER_NAME_CHECKED(UMjGeom, DefaultClass))
{
if (DefaultClass)
MjClassName = DefaultClass->ClassName;
else
MjClassName.Empty();
}

// Apply OverrideMaterial to the visual mesh
if (PropertyName == GET_MEMBER_NAME_CHECKED(UMjGeom, OverrideMaterial) ||
MemberPropertyName == GET_MEMBER_NAME_CHECKED(UMjGeom, OverrideMaterial))
{
ApplyOverrideMaterial(OverrideMaterial);
}
}
#endif

void UMjGeom::ApplyOverrideMaterial(UMaterialInterface* Material)
{
// Base implementation is a no-op. Mesh geoms should not have their imported
// materials overwritten — they already have materials from the import pipeline.
// Primitive subclasses (Box, Sphere, Cylinder) override this with direct
// VisualizerMesh access.
}

void UMjGeom::RegisterToSpec(FMujocoSpecWrapper& Wrapper, mjsBody* ParentBody)
{
if (!ParentBody) return;
Expand Down Expand Up @@ -1027,3 +1052,10 @@ void UMjGeom::RemoveDecomposition()
void UMjGeom::DecomposeMesh() {}
void UMjGeom::RemoveDecomposition() {}
#endif

#if WITH_EDITOR
TArray<FString> UMjGeom::GetDefaultClassOptions() const
{
return GetSiblingComponentOptions(this, UMjDefault::StaticClass(), true);
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,17 @@
// limitations under the License.
//
// --- LEGAL DISCLAIMER ---
// UnrealRoboticsLab is an independent software plugin. It is NOT affiliated with,
// endorsed by, or sponsored by Epic Games, Inc. "Unreal" and "Unreal Engine" are
// UnrealRoboticsLab is an independent software plugin. It is NOT affiliated with,
// endorsed by, or sponsored by Epic Games, Inc. "Unreal" and "Unreal Engine" are
// trademarks or registered trademarks of Epic Games, Inc. in the US and elsewhere.
//
// This plugin incorporates third-party software: MuJoCo (Apache 2.0),
// This plugin incorporates third-party software: MuJoCo (Apache 2.0),
// CoACD (MIT), and libzmq (MPL 2.0). See ThirdPartyNotices.txt for details.
#pragma once

#include "CoreMinimal.h"
#include "IDetailCustomization.h"
#include "MuJoCo/Components/Geometry/MjMeshGeom.h"

class FMjGeomDetailCustomization : public IDetailCustomization
UMjMeshGeom::UMjMeshGeom()
{
public:
static TSharedRef<IDetailCustomization> MakeInstance();
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
};
Type = EMjGeomType::Mesh;
bOverride_Type = true;
}
7 changes: 7 additions & 0 deletions Source/URLab/Private/MuJoCo/Components/Geometry/MjSite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,10 @@ void UMjSite::Bind(mjModel* model, mjData* data, const FString& Prefix)
UE_LOG(LogURLabBind, Warning, TEXT("[MjSite] Site '%s' FAILED bind. Prefix: %s, MjName: %s"), *GetName(), *Prefix, *MjName);
}
}

#if WITH_EDITOR
TArray<FString> UMjSite::GetDefaultClassOptions() const
{
return GetSiblingComponentOptions(this, UMjDefault::StaticClass(), true);
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ void UMjBox::OnRegister()
VisualizerMesh->SetupAttachment(this);
VisualizerMesh->RegisterComponent();
}

// Apply override material after mesh is created and registered
if (VisualizerMesh && OverrideMaterial && IsValid(OverrideMaterial) && GetOwner())
{
VisualizerMesh->SetMaterial(0, OverrideMaterial);
}
}


Expand Down Expand Up @@ -115,6 +121,15 @@ void UMjBox::ExportTo(mjsGeom* geom, mjsDefault* def)
Super::ExportTo(geom, def);
}

void UMjBox::ApplyOverrideMaterial(UMaterialInterface* Material)
{
EnsureVisualizerMesh();
if (VisualizerMesh && Material && IsValid(Material))
{
VisualizerMesh->SetMaterial(0, Material);
}
}

void UMjBox::SyncUnrealTransformFromMj()
{
if (m_GeomView.id == -1) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ void UMjCylinder::OnRegister()
VisualizerMesh->SetupAttachment(this);
VisualizerMesh->RegisterComponent();
}

if (VisualizerMesh && OverrideMaterial && IsValid(OverrideMaterial) && GetOwner())
{
VisualizerMesh->SetMaterial(0, OverrideMaterial);
}
}


Expand Down Expand Up @@ -108,6 +113,12 @@ void UMjCylinder::ExportTo(mjsGeom* geom, mjsDefault* def)
Super::ExportTo(geom, def);
}

void UMjCylinder::ApplyOverrideMaterial(UMaterialInterface* Material)
{
EnsureVisualizerMesh();
if (VisualizerMesh && Material && IsValid(Material)) VisualizerMesh->SetMaterial(0, Material);
}

void UMjCylinder::SyncUnrealTransformFromMj()
{
if (m_GeomView.id == -1) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ void UMjSphere::OnRegister()
VisualizerMesh->SetupAttachment(this);
VisualizerMesh->RegisterComponent();
}

if (VisualizerMesh && OverrideMaterial && IsValid(OverrideMaterial) && GetOwner())
{
VisualizerMesh->SetMaterial(0, OverrideMaterial);
}
}


Expand Down Expand Up @@ -104,6 +109,12 @@ void UMjSphere::ExportTo(mjsGeom* geom, mjsDefault* def)
Super::ExportTo(geom, def);
}

void UMjSphere::ApplyOverrideMaterial(UMaterialInterface* Material)
{
EnsureVisualizerMesh();
if (VisualizerMesh && Material && IsValid(Material)) VisualizerMesh->SetMaterial(0, Material);
}

void UMjSphere::SyncUnrealTransformFromMj()
{
if (m_GeomView.id == -1) return;
Expand Down
7 changes: 7 additions & 0 deletions Source/URLab/Private/MuJoCo/Components/Joints/MjJoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -512,3 +512,10 @@ FString UMjJoint::GetTelemetryTopicName() const
{
return FString::Printf(TEXT("joint/%s"), *GetName());
}

#if WITH_EDITOR
TArray<FString> UMjJoint::GetDefaultClassOptions() const
{
return GetSiblingComponentOptions(this, UMjDefault::StaticClass(), true);
}
#endif
51 changes: 51 additions & 0 deletions Source/URLab/Private/MuJoCo/Components/MjComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@
#include "MuJoCo/Components/Defaults/MjDefault.h"
#include "MuJoCo/Components/Bodies/MjBody.h"

#if WITH_EDITOR
#include "Engine/Blueprint.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "Engine/SimpleConstructionScript.h"
#include "Engine/SCS_Node.h"
#endif

UMjComponent::UMjComponent()
{
PrimaryComponentTick.bCanEverTick = true;
Expand Down Expand Up @@ -120,3 +127,47 @@ UMjDefault* UMjComponent::FindEditorDefault() const

return nullptr;
}

#if WITH_EDITOR
TArray<FString> UMjComponent::GetSiblingComponentOptions(const UObject* CallerComponent, UClass* FilterClass, bool bIncludeDefaults)
{
TArray<FString> Options;
Options.Add(TEXT("")); // Empty = no selection

if (!CallerComponent || !FilterClass) return Options;

// Walk the outer chain to find the owning Blueprint
UBlueprint* BP = nullptr;
for (UObject* Outer = CallerComponent->GetOuter(); Outer; Outer = Outer->GetOuter())
{
if (UBlueprintGeneratedClass* BPGC = Cast<UBlueprintGeneratedClass>(Outer))
{
BP = Cast<UBlueprint>(BPGC->ClassGeneratedBy);
break;
}
if (UBlueprint* Found = Cast<UBlueprint>(Outer))
{
BP = Found;
break;
}
}

if (!BP || !BP->SimpleConstructionScript) return Options;

for (USCS_Node* Node : BP->SimpleConstructionScript->GetAllNodes())
{
UMjComponent* MjComp = Cast<UMjComponent>(Node->ComponentTemplate);
if (MjComp && MjComp->IsA(FilterClass))
{
if (MjComp->bIsDefault && !bIncludeDefaults) continue;

FString DisplayName = MjComp->MjName.IsEmpty()
? Node->GetVariableName().ToString()
: MjComp->MjName;
Options.Add(DisplayName);
}
}

return Options;
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include "XmlFile.h"
#include "mujoco/mujoco.h"
#include "MuJoCo/Core/Spec/MjSpecWrapper.h"
#include "MuJoCo/Components/MjComponent.h"
#include "MuJoCo/Components/Bodies/MjBody.h"
#include "Utils/URLabLogging.h"

UMjContactExclude::UMjContactExclude()
Expand Down Expand Up @@ -74,3 +76,10 @@ void UMjContactExclude::Bind(mjModel* model, mjData* data, const FString& Prefix
{
// Contact excludes are global static data.
}

#if WITH_EDITOR
TArray<FString> UMjContactExclude::GetBodyOptions() const
{
return UMjComponent::GetSiblingComponentOptions(this, UMjBody::StaticClass());
}
#endif
Loading