- 2025-04-02
-
发表了主题帖:
跟我学:每天5分钟,一个月入门mujoco(1.4)
书接上回(估计鸽了一个月了吧),上次我们从高空丢下一个红球和一个蓝球,现在我们换两个东西丢下来:
[localvideo]1bdd8210a344b2175a36449f67eca30a[/localvideo]
一个恐龙玩具和一个人偶
其中恐龙玩具是stl模型,人偶是从mujoco自带的humanoid.xml里的素材“torso”,使用xml模型剪切出来。
附件:
代码如下:
import mujoco
import mujoco.viewer
import time
from xml.dom import minidom
def load_xml(xml):
dom = minidom.parse(xml)
return dom
def get_root_element(dom):
if dom is not None:
root = dom.documentElement
return root
return None
def get_child_elements(root,tag):
if root is not None:
child_elements = root.getElementsByTagName(tag)
return child_elements
return None
def make_object(dom,obj):
newnode = dom.createElement("geom")
for k,v in obj.items():
newnode.setAttribute(k,v)
return newnode
def make_object(dom,obj,file_name,scale = "1 1 1"):
root = get_root_element(dom)
asset = get_child_elements(root,"asset")
if asset == []:
asset = dom.createElement("asset")
root.appendChild(asset)
else:
asset = asset[0]
mesh = dom.createElement("mesh")
mesh.setAttribute("file",file_name)
mesh.setAttribute("name",obj["mesh"])
mesh.setAttribute("scale",scale)
asset.appendChild(mesh)
newnode = dom.createElement("geom")
for k,v in obj.items():
newnode.setAttribute(k,v)
return newnode
def add_object_neo(pnode,newnode,geom_in_body_color=None):
if newnode.nodeName == "geom":
tmpbody = dom.createElement("body")
tmpnode = dom.createElement("joint")
tmpnode.setAttribute("type","free")
tmpbody.appendChild(tmpnode)
tmpbody.appendChild(newnode)
pnode.appendChild(tmpbody)
if newnode.nodeName == "body":
if geom_in_body_color:
geoms = get_child_elements(newnode,"geom")
for geom in geoms:
geom.setAttribute("rgba", geom_in_body_color)
pnode.appendChild(newnode)
def get_body(obj_file,obj_name):
obj_dom = minidom.parse(obj_file)
obj_nodes = obj_dom.getElementsByTagName("body")
for obj_node in obj_nodes:
if obj_node.getAttribute("name") == obj_name:
return obj_node
if __name__ == "__main__":
dom = load_xml("tower.xml")
root = get_root_element(dom)
pnode = get_child_elements(root, "worldbody")[0]
Aristotle = get_body("humanoid.xml","torso")
Aristotle.setAttribute("pos","16 1.5 31.6")
add_object_neo(pnode,Aristotle,"0 0 .9 1")
file_name = "triceratops.stl"
triceratops = {}
triceratops["type"] = "mesh"
triceratops["pos"] = "16 -1.5 30"
triceratops["mesh"] = "dino"
triceratops["rgba"] = ".9 0 0 1"
triceratops["euler"] = "90 0 0"
newnode = make_object(dom,triceratops,file_name,".08 .08 .08")
add_object_neo(pnode,newnode)
model = mujoco.MjModel.from_xml_string(dom.toxml())
data = mujoco.MjData(model)
viewer = mujoco.viewer.launch_passive(model, data)
viewer.cam.azimuth = 145.0
viewer.cam.distance = 90
viewer.cam.elevation = -5.0
while True:
mujoco.mj_step(model, data)
viewer.sync()
time.sleep(0.001)
好困,不想写代码分析了,把代码注释粘下面:
-
回复了主题帖:
MuJoCo 机械臂物体碰撞、接触检测方式一
蓝雨夜 发表于 2025-4-2 08:37
下次有没有安排从硬件慢慢搭建,选型,到调试的啊
mujoco啊,我来做教程好了。。。就是很懒,写了几个字就开始摸鱼了。。。
- 2025-03-25
-
发表了主题帖:
【新年点灯】总结——Raspberry Pi Pico驱动WS2812全彩LED矩阵
点灯总结——Raspberry Pi Pico驱动WS2812全彩LED矩阵
利用微控制器驱动LED矩阵实现动态显示,是非常基础也是非常常见的应用了,在本次点灯活动中,基于Raspberry Pi Pico开发板,通过MicroPython编程驱动WS2812全彩LED灯板,成功复现了经典游戏角色及场景动画。
0x01 项目背景与思路
动机溯源
利用业余时间探索Pico的硬件潜能,结合手头WS2812灯板资源,通过低成本方案实现高精度全彩动态显示。
技术可行性分析
WS2812采用单线级联架构,利用非归零编码传输24位RGB数据,天然支持长链级联。Pico的PIO(可编程I/O)引擎配合状态机架构,可以精确显示到每一个像素,且保持较高的刷新速率。
0x02 硬件系统设计
[电源] 5V───┬──────────┬──────────┬──────────┬──────────┬──LED灯带
│ │ │ │ └──(并联结构)
▼ ▼ ▼ ▼
| LED灯带 | WS2812B 256颗 | 全彩显示单元,级联模式工作 |
| 电源模块 | 5V/5A电源 | 独立供电防止主控板过载 |
| 信号接口 | 杜邦线+排针 | Pico GP0引脚→LED数据输入端 |
采用rpi的pico的pin0做控制信号,连接在ws2812的输入端;虽然在ws2812板上每颗灯只需要大概0.03A的电流,所有灯珠电源的正负极都是并联起来的,由于使用灯数较多(256颗),因此对电流要求相对较高,专门准备了一个5V5A电源对其供电。在实际使用中,并不是所有灯都全部点亮,因此5A的电流足以驱动。
0x03 软件实现
我们知道WS2812是采用单线通信协议(非归零编码),24位RGB数据依次传输(每LED截取前24bit后转发剩余数据),灯珠的控制信号彼此串联,第一个灯的信号输出就是第二个灯的输入,每过一个灯,去除自己所用的数据,将剩下的bit流给下一个灯珠。
因此只要发出一段连续bit码,就能够实现点亮全部灯,并且由于灯的颜色不同,可以组成不同图案;通过保持供电,图案不会发生变化;在下一时间段内,发送另一段bit码,就能修改图案内容。
因此只需要考虑如何发送这些bit码就可以了,树莓派pico有自己的点灯库,我这里调用的是array、time、machine中的Pin等,都是现成的。
第一步是把树莓派pico改造成mpython支持,将MicroPython固件刷入Raspberry Pi Pico。
第二步是程序设计,框架基本类似,都是
定义PIO程序:使用@rp2.asm_pio 装饰器定义一个PIO程序,用于生成WS2812灯的控制信号。
创建StateMachine:使用rp2.StateMachine创建状态机,将PIO程序与指定引脚关联。
启动StateMachine:调用sm.active(1)启动状态机。
第三步是将想显示的图像转存为数组,由于四块ws2812的灯板连接方式比较特别,如下:
所以我专门编写了一段代码来实现这个工作,将16*16的图像转到字节组中,再显示出来。
第四步,将py文件置入pico中。
第五步,加电测试。
0x04 动画效果
如前面所述,动画效果的实现只需要更换bit流就可以了,因此准备多幅图片替换显示。在ghost项目中,增加了随机值,由于每次更换的图片相对随机,所以把图片拆解成多个部分,在程序中重新组合起来。
0x05 成果帖和源文件下载
宝可梦系列中的经典精灵球:
https://bbs.eeworld.com.cn/thread-1305846-1-1.html
塞尔达传说的主角林克:
https://bbs.eeworld.com.cn/thread-1305950-1-1.html
https://bbs.eeworld.com.cn/thread-1306139-1-1.html
FC食豆人中的第1位敌人——Blinky
https://bbs.eeworld.com.cn/thread-1306543-1-1.html
补充:
游戏中的经典形象我都是手绘以后再转储的,下次如果有机会拿个解析更高的显示,可以搞sonic和tails……
- 2025-03-09
-
回复了主题帖:
【新年花灯秀】彩色PCB雪花灯
漂亮
- 2025-03-01
-
回复了主题帖:
想知道两轮差速方形底盘 URDF 咋做,ROS2 配 Rviz 咋显示吗?看这里!
学习学习
- 2025-02-28
-
回复了主题帖:
跟我学:每天5分钟,一个月入门mujoco(序)
freebsder 发表于 2025-2-28 16:29
这是个仿真平台还是个开发平台呢?
可以把这个当作是一个游戏引擎:把一个完整游戏里面的素材都删掉,但保留了接口。
就像马里奥制造那样的,各种砖块都放在那里,你想搭一个什么关卡都可以,等你搭好了,就可以尝试过关(类似仿真),但是你也可以你的存档给别人,别人可以在你基础上继续修改(类似开发)。
- 2025-02-27
-
回复了主题帖:
跟我学:每天5分钟,一个月入门mujoco(1.3)
jryq213 发表于 2025-2-27 12:33
非常错的资料,学习了,如果有详细课件更好。
有源码,全套代码我是我自己敲的。
需要ppt么?那可能得重新写
-
回复了主题帖:
跟我学:每天5分钟,一个月入门mujoco(1.3)
wangerxian 发表于 2025-2-27 08:57
可以配置小球的密度或者材质吗?
可以的,下下下……节就有了
下节是初速度
下下节是采集数据和生成曲线
再往后就是改变各种外部参数,比如重力、风力(风阻)、密度等等
- 2025-02-26
-
发表了主题帖:
跟我学:每天5分钟,一个月入门mujoco(1.3)
第3节 自由落体
在以比萨斜塔为背景,让两个小球从高处落下:
[localvideo]f194ab1f63194a35a1dd18fae011e4a6[/localvideo]
可以看到,存在一个加速下落的过程。
代码如下:
import mujoco
import mujoco.viewer
import time
from xml.dom import minidom
def load_xml(xml):
dom = minidom.parse(xml)
return dom
def get_root_element(dom):
if dom is not None:
root = dom.documentElement
return root
return None
def get_child_elements(root,tag):
if root is not None:
child_elements = root.getElementsByTagName(tag)
return child_elements
return None
def make_object(dom,obj):
newnode = dom.createElement("geom")
for k,v in obj.items():
newnode.setAttribute(k,v)
return newnode
def add_object(dom,pnode,newnode):
if newnode.nodeName == "geom":
tmpbody = dom.createElement("body")
tmpnode = dom.createElement("joint")
tmpnode.setAttribute("type","free")
tmpbody.appendChild(tmpnode)
tmpbody.appendChild(newnode)
pnode.appendChild(tmpbody)
if __name__ == "__main__":
red_ball = {}
red_ball["type"] = "sphere"
red_ball["pos"] = "16 -1.5 30"
red_ball["size"] = "1"
red_ball["rgba"] = ".9 0 0 1"
blue_ball = {}
blue_ball["type"] = "sphere"
blue_ball["pos"] = "16 1.5 30"
blue_ball["size"] = "1"
blue_ball["rgba"] = "0 0 .9 1"
dom = load_xml("tower.xml")
root = get_root_element(dom)
pnode = get_child_elements(root, 'worldbody')[0]
newnode = make_object(dom,red_ball)
add_object(dom,pnode,newnode)
newnode = make_object(dom,blue_ball)
add_object(dom,pnode,newnode)
model = mujoco.MjModel.from_xml_string(dom.toxml())
data = mujoco.MjData(model)
viewer = mujoco.viewer.launch_passive(model, data)
viewer.cam.azimuth = 145.0
viewer.cam.distance = 90
viewer.cam.elevation = -5.0
while True:
mujoco.mj_step(model, data)
viewer.sync()
time.sleep(0.001)
分析:
这里有两个函数
def make_object(dom,obj):
newnode = dom.createElement("geom")
for k,v in obj.items():
newnode.setAttribute(k,v)
return newnode
def add_object(dom,pnode,newnode):
if newnode.nodeName == "geom":
tmpbody = dom.createElement("body")
tmpnode = dom.createElement("joint")
tmpnode.setAttribute("type","free")
tmpbody.appendChild(tmpnode)
tmpbody.appendChild(newnode)
pnode.appendChild(tmpbody)
make_object是用于生成一个新的geom(几何体),是将刚生成的几何体放置在之前的dom中。
在前面章节里面,我们知道,如果不做设置,同一个body内的的几何体相对位置是固定的,也就是说,即使我们放置了geom,它也会悬在半空。为了实现下落的要求,我们增加了一个属性类型为”free”的joint(关节),这里新引入了joint的概念,它是用于连接几何体的虚拟构件。主要属性有:
name:名字
pos:关节位置
type:关节类型,可以是hinge(铰链关节,绕单一轴旋转)、slide(滑动关节,沿单一轴线移动)、ball(球形关节)、free(自由型),缺省是hinge
axis:轴,可以用于滚动(hinge)或者滑动slide
- 2025-02-25
-
回复了主题帖:
跟我学:每天5分钟,一个月入门mujoco(1.2)
wangerxian 发表于 2025-2-25 20:39
这是一个3D库吗?可以导入什么样的3D文件?
事实上,它的3D功能好像还真不怎么样,基础功能只有方块、球、椭球、连三角块都需要外部建模,支持stl格式的mesh导入……
而且渲染功能我一直没搞定,但物理特性仿真不错(特指固体)。
- 2025-02-24
-
发表了主题帖:
跟我学:每天5分钟,一个月入门mujoco(1.2)
第2节 设置视角
在前1节,我们建立了模型并且将其存储为一个xml文件,但我们发现,这个模型相对我们的显示界面“太大”了,需要用鼠标调整以后才能摆在屏幕中间,至于怎么调整,相信大家都很熟悉,左键拖动旋转,右键移动位置,滚轮缩放。
在本节,我们复习载入xml文件,并学习在代码中变换视角。
[localvideo]67a900b20b8bda66810078b99025e377[/localvideo]
代码其实非常简单,就是不断变换几个参数:
import mujoco
import mujoco.viewer
import time
if __name__ == "__main__":
model = mujoco.MjModel.from_xml_path('tower.xml')
data = mujoco.MjData(model)
viewer = mujoco.viewer.launch_passive(model, data)
for i in range(0,960):
viewer.cam.elevation= 0 - i/65
viewer.cam.distance = i/15+40
viewer.cam.azimuth = i/2
viewer.cam.lookat = [0, 0, -15+i/55]
time.sleep(0.02)
由于代码实在太简单,本节不做代码讲解。
-
回复了主题帖:
跟我学:每天5分钟,一个月入门mujoco(1.1)
hellokitty_bean 发表于 2025-2-24 08:57
让人好好奇呀。。。。。。。。。。mujoco这么可爱好玩吗
如果当游戏玩的话,建议“马里奥制造”,如果想用来当物理学教具,我认为初中的所有实验都够了
- 2025-02-23
-
发表了主题帖:
跟我学:每天5分钟,一个月入门mujoco(1.1)
重力加速度
在本章中,我们尝试编写一个模型测试物体在重力场中的行为,并尝试修改各项参数(质量、形状、重力加速度等),看看如何在mujoco的呈现。
第1节 坐标系与参照物
说起重力加速度,最出名的肯定是伽利略先生在比萨斜塔上做的自由落体实验。多年前我也曾前往斜塔朝圣,这里就以斜塔作为落体的参照物吧。
这一节我们先学着画参照物:
(照片来自网络,侵权请告知)
可以看到塔分8层,每层的结构相似,由柱子和塔身构成,可以抽象为使用若干大大小小的圆柱组合而成。
如前一章节中介绍,可以使用xml格式文件来存储mujoco模型。那么现在就介绍一下mujoco里面常用的xml标签元素:
mujoco:
这是最外层的标签元素,也就是所谓的根,全xml只能有一个,有属性值model,缺省是“MuJoCo Model”,在viewer中显示的标题。
body(体):
body是在mujoco中用于指定物体的标签元素(支持套嵌,最外层的叫做worldbody),可以包括的属性有:
name:体的名字,定义后可以用name来引用这个体
pos:体的位置,当然是基于上级元素的,缺省是(0,0,0)
euler:控制体的方向,也是基于上级元素的
Geom(几何体):
name:名字
pos:几何体位置
type:几何体类型,可以是sphere(球体,缺省值)、plane(地平面)、box(立方体)、cylinder(圆柱体)、capsule(胶囊体)、ellipsoid(橄榄球形)等等
size:几何体大小
rgba:渲染几何体的颜色
知道了上述信息,就能开始了,首先,我们具有简单python基础,编写xml文件可以用dom。
import mujoco
import mujoco.viewer
import xml.dom.minidom as minidom
import math
def create_root(dom):
root = dom.documentElement
root.setAttribute("model", "Elizabeth Tower")
return root
def create_world(dom,root):
worldbody = dom.createElement("worldbody")
root.appendChild(worldbody)
plane = dom.createElement("geom")
plane.setAttribute("type","plane")
plane.setAttribute("size","20 20 0.1")
plane.setAttribute("rgba","0 .8 0 1")
plane.setAttribute("pos","0 0 -22")
ground = dom.createElement("geom")
ground.setAttribute("type","box")
ground.setAttribute("name","ground")
ground.setAttribute("size","20 20 1")
ground.setAttribute("rgba",".237 .189 .101 1")
ground.setAttribute("pos","0 0 -23")
worldbody.appendChild(plane)
worldbody.appendChild(ground)
towerbody = create_tower(dom)
towerbody.setAttribute("pos","0 0 -23")
towerbody.setAttribute("euler","0 5 0")
worldbody.appendChild(towerbody)
def create_tower(dom):
towerbody = dom.createElement("body")
towerbody.setAttribute("name","Elizabeth Tower")
towerbase = dom.createElement("geom")
towerbase.setAttribute("type","cylinder")
towerbase.setAttribute("rgba",".5 .5 .5 1")
towerbase.setAttribute("pos","0 0 5")
towerbase.setAttribute("size","9 5")
towerbody.appendChild(towerbase)
for i in range(15):
a=11
pole = dom.createElement("geom")
pole.setAttribute("type","cylinder")
pole.setAttribute("rgba",".5 .5 .5 1")
pole.setAttribute("pos",str(a*math.sin(i*math.pi/7.5))+" "+str(a*math.cos(i*math.pi/7.5))+" 5")
pole.setAttribute("size",".7 5")
towerbody.appendChild(pole)
top1 = dom.createElement("geom")
top1.setAttribute("type","cylinder")
top1.setAttribute("rgba",".5 .5 .5 1")
top1.setAttribute("pos","0 0 10")
top1.setAttribute("size","12.1 .1")
towerbody.appendChild(top1)
a = 10.8
for level in range(1,7):
top = dom.createElement("geom")
top.setAttribute("type","cylinder")
top.setAttribute("rgba",".5 .5 .5 1")
top.setAttribute("pos","0 0 "+str(10+level*6))
top.setAttribute("size",str(a+0.8)+" .1")
towerbody.appendChild(top)
for i in range(30):
pole = dom.createElement("geom")
pole.setAttribute("type","cylinder")
pole.setAttribute("rgba",".5 .5 .5 1")
pole.setAttribute("pos",str(a*math.sin(i*math.pi/15))+" "+str(a*math.cos(i*math.pi/15))+" "+str(7+6*level))
pole.setAttribute("size",".5 3")
towerbody.appendChild(pole)
stair = dom.createElement("geom")
stair.setAttribute("type","cylinder")
stair.setAttribute("rgba",".5 .5 .5 0.5")
stair.setAttribute("pos","0 0 "+str(7+6*level))
stair.setAttribute("size",str(a-2)+" 3")
towerbody.appendChild(stair)
a=a-0.15
towertop = dom.createElement("geom")
towertop.setAttribute("type","cylinder")
towertop.setAttribute("rgba",".5 .5 .5 1")
towertop.setAttribute("pos","0 0 50")
towertop.setAttribute("size","7 4")
towerbody.appendChild(towertop)
for i in range(12):
a=8
pole = dom.createElement("geom")
pole.setAttribute("type","cylinder")
pole.setAttribute("rgba",".5 .5 .5 1")
pole.setAttribute("pos",str(a*math.sin(i*math.pi/6))+" "+str(a*math.cos(i*math.pi/6))+" 50")
pole.setAttribute("size",".4 4")
towerbody.appendChild(pole)
top2 = dom.createElement("geom")
top2.setAttribute("type","cylinder")
top2.setAttribute("rgba",".5 .5 .5 1")
top2.setAttribute("pos","0 0 54")
top2.setAttribute("size","9 .3")
towerbody.appendChild(top2)
return towerbody
if __name__ == "__main__":
dom = minidom.getDOMImplementation().createDocument(None,"mujoco",None)
root = create_root(dom)
create_world(dom,root)
m = mujoco.MjModel.from_xml_string(dom.toxml())
v = mujoco.viewer.launch(m)
with open('tower.xml', 'w', encoding='utf-8') as f:
dom.writexml(f, addindent='\t', newl='\n',encoding='utf-8')
执行一下看看,嗯,由于模型太大了,可能看不到全图,可以用鼠标滚轮操作一下缩放:
代码分析:
dom = minidom.getDOMImplementation().createDocument(None,"mujoco",None)
新建dom,其中根叫做mujoco
root = dom.documentElement
root.setAttribute("model", "Elizabeth Tower")
获取根节点,指定model属性(显示名为Elizabeth Tower)。
worldbody = dom.createElement("worldbody")
root.appendChild(worldbody)
在root下增加worldbody。
with open('tower.xml', 'w', encoding='utf-8') as f:
dom.writexml(f, addindent='\t', newl='\n',encoding='utf-8')
保存xml文件,命名为tower.xml。
- 2025-02-21
-
回复了主题帖:
恭喜LitchiCheng走马上任机器人开发版块版主!!
恭喜恭喜
-
发表了主题帖:
跟我学:每天5分钟,一个月入门mujoco(序)
机器人仿真是一个很有趣的话题,多年前我曾尝试用过不少游戏引擎去学习“制作”机器人,但最终比较倾向于使用webots和mujoco。
这里可以开两个两个讨论话题:webots、mujoco。
Mujoco也是比较神奇的,最初免费,不少人学习使用;后来收费,损失了大量人气,再后来被deepmind买下,直接开源了,但已经不复当年盛况,在中文的各论坛上也基本找不到什么新一点有深度的资料——几年前的还有,但已经不再适配最新版本了。
有鉴于此,笔者准备为爱发电,借eeworld宝地开一个mujoco系列。大家可以试着跟读,每天只占用5分钟阅读时间,大概一个月,就能把这个有趣物理引擎玩转了。当然我不保证每天都能更新。
mujoco是什么?
Multi-Joint dynamics with Contact,翻译起来可以认为是多关节动力学连接。这也是mujoco的主要用途。可以快速开发多个物体的连接,并且对其物理特性进行仿真。具体使用什么语言去开发,其实并不重要,看大家的习惯吧。在本系列的文章中,我们从实战入手,使用python进行开发,且假设各位都已经有一点点python的开发基础了。
第0章 安装环境和显示一个mujoco模型
在本章的练习中,我们将使用到mujoco的传统例子——机械手臂。
首先,我们准备一下开发环境,使用pip install mujoco安装mujoco的python支持(这里最好是python3.10以上的版本,本人使用的是python3.11)。需要用到的xml文件(arm26.xml)和本节所用到的代码见附件。
我们先介绍一下几个基础概念:
模型
在mujoco中,模型是可以用于定义显示和操作的,可以使用xml存储。
视图
Viewer是mujoco最重要的组成部分,用于呈现出界面。一般来说,我们可以通过命令行调用:
python -m mujoco.viewer --mjcf=arm26.xml
执行该命令后,可以看到一个所谓2连接6肌肉的手臂,可以通过按钮来控制这个手臂
[localvideo]e1308ec00eb9ee7d1b90c454b7ab00e9[/localvideo]
用代码载入
#直接pip install mujoco安装
import mujoco.viewer
m = mujoco.MjModel.from_xml_path('arm26.xml')
v = mujoco.viewer.launch(m)
核心就两条,因为太过简单,就不怎么介绍了吧
m = mujoco.MjModel.from_xml_path('arm26.xml')
v = mujoco.viewer.launch(m)
本章节用到模型xml文件
-
回复了主题帖:
撒积分啦!!机器人开发圈公众号上线、还有多个新板块设立哦~~
恭喜恭喜
-
回复了主题帖:
四足机器人学习资料
freebsder 发表于 2025-2-20 19:21
arduino控制的?应该没那么智能吧
确实是arduino。。。
- 2025-02-20
-
上传了资料:
四足机器人
-
发表了主题帖:
四足机器人学习资料
上传两个我珍藏模型——以前一直以为有时间能弄出来玩玩的,一拖就是几年
[localvideo]187a7cfaf2d55df5399c29f4a5496992[/localvideo]
-
回复了主题帖:
【过年都玩啥】话说探友们过年都玩啥了呢?
回乡炸牛屎。。。