introduce
Pixyz product official website
Download, installation and technical documentation
Official website introduction: Pixyz supports more than 45 industrial file formats, including CATIA, JT, STEP, IFC, PVZ, NWD, USD and glTF. Includes CAD, tessellation/mesh models, point clouds, and more. Assets in Unity will be linked to the original data in real time, automatically updating as file modifications occur. Create LODs and UVs, merge and disassemble meshes, change pivots, and more in Unity to build ready-to-use assets. Optimize any data in the Unity Editor without re-importing it.
There are 4 product lines:
- pixyz studio: standalone software tool
- Import/convert CAD, mesh and point cloud data
- Optimize/export data
- Automate tasks with the Python API
- pixyz plugin: A plug-in installed in unity, specially customized for unity. The process of exporting CAD models and importing them into unity is better than exporting from studio and then importing into unity.
- Import CAD/3D/BIM data into Unity
- Preserve hierarchy and metadata
- Automatically run scripts with rules engine
- Pixyz Review
- Drag and drop CAD, mesh or point cloud data
- Enter virtual reality with one click
- Research and interact with models
- Pixyz Scenario Processor
- Batch conversion and model optimization
- Run on AWS Cloud or standalone machine
- Provide expansion possibilities
Version comparison
VersionComparison of different versions, you can see the content and status of each update
Supported file formats
Supported file formats for import and export are available in the documentation:Import/Export file formats
Getting started
Only some simple contents of pixyz studio and pixyz Scenario Processor are introduced.
Pixyz Studio
It is an independent software with a visual interface. After installation and activation (free trial available), model processing can be performed.
Contains the most basic import, export and other functions
import
In the upper left corner, click the small arrow to select Guided import or Raw import.
After selecting Guided Import, you can select the file and configure it according to the preset, select the read quality, coordinate settings, import points and lines, etc.
Click Execute to start importing the model
At this time, you can see that the Output in the lower left corner of the model begins to output, which is the command executed during the process of importing the model (executing command xxxxx
). For example, the import is executed here:
executing command process.guidedImport
executing command scene.deleteEmptyAnimation
executing command scene.applyTransformation
executing command scene.cleanUnusedMaterials
executing command scene.removeSymmetryMatrices
After importing, you can see the tree structure of the model in Occurrences on the left:
Visually see the model in the largest Viewer window in the middle
other functions
The top navigation bar has common CAD, patch, model optimization and other functions
Let’s take Decimate To Target as an example:
You can choose to reduce the substructure belonging to a certain occurrence. If not selected, the default is the root directory (Root). You can choose the target strategy:
- Triangle Count: Reduce the number of triangles to a certain number (default 10,000)
- Ratio: reduced to a certain ratio (default 50%)
Here, set the surface reduction to 50% (Ratio), select Execute, and start reducing the surface.
Similarly, you can see the execution process and results of surface subtraction in Output:
Export
Click Export to export the imported model. When exporting, it will be converted according to the file type you set.
Therefore, as long as you first import a model in a certain format and then export it to another format, you can complete the model type conversion.
For example, import '.fbx' and export '.prefab', which can convert the fbx model into a unity prefab.
In the Output in the lower left corner, you can see that the export command is executed:
Execute python script
You can see that there is a Scripting window next to Output, which can be used to write and run python scripts
Click "+" to create a new python script. Click Sample Scripts to open the local folder. When installing Pixyz Studio, script samples are also downloaded. You can open each sample to view and learn.
Example,101_GenericProcess.py
:
# Pixyz Python API sample (2022.1)
#
# 101 Generic Process
#
# Example of a typical simple process to transform a CAD model into optimized mesh(es)
from pxz import *
def process():
allScene = [scene.getRoot()]
# repair CAD with 0.1mm tolerance
algo.repairCAD(allScene, 0.1, True)
# tessellates with maxSag=0.2mm and maxAngle=10deg
algo.tessellate(allScene, 0.2, -1, 10)
# repairs with a tolerance of 0.1mm
algo.repairMesh(allScene, 0.1)
# removes through holes with diameter under 10mm
algo.removeHoles(allScene, True, False, False, 10)
# removes hidden patches
algo.hiddenRemoval(allScene,algo.SelectionLevel.Patches, 1024, 16)
# deletes patches to allow the decimation to remesh over the base CAD patches
algo.deletePatches(allScene)
# decimates with surfacic tolerance to 1mm, lineic tolerance to 0.1mm and normal distorsion tolerance to 5mm
algo.decimate(allScene, 1, 0.1, 5)
# processes the model
process()
You can copy this line of code into the Script window and execute it
For more information, you can view the official Python API
But there is a simpler method in Pixyz Studio. During the execution of tasks in the previous interface, you can see that there is a button on each separate window:Copy python code to clipboard, such as import window and surface reduction window:
After clickCopy python code to clipboard button, you can copy the code to the Script window:
Python code to import the model:
_ret_ = process.guidedImport(["D:/furniture_knoll_for_offices.dwg"], pxz.process.CoordinateSystemOptions(["automaticOrientation",0], ["automaticScale",0], False, False), ["usePreset",2], pxz.process.ImportOptions(False, True, True), True, True, False, False, False, False)
Python code for reducing model faces:
algo.decimateTarget([1], ["ratio",50.000000000000], 0, False, 5000000)
After clicking Run, you can get the same effect as using the visualization panel to reduce the surface:
Scripts can be used for unified process processing, but there are still some minor problems when importing and exporting.
To batch process multiple models, you can use the Pixyz Scenario Processor
Pixyz Scenario Processor
How to run the Pixyz Scenario Processor
Refer to the official documentation: How to run Pixyz Scenario Processor
Currently, there are two execution methods for referring to windows commands:
- python script: executed directly as a python script
- plugin: compiled into
.pxzext
for plug-in use
It also provides Scenario'sscript sample. The following directly refers to the code in the gitlab library for magic modification.
Scenario Processor sample
There are three main folders in the warehouse. The readme gives an introduction (very abstract). I will explain it based on my personal understanding:
- Folder Watcher: Monitors the folder on the local hard disk. All files placed in this folder will execute a python script and perform corresponding operations< a i=2> 2a. Scenario sample: Publish a python script into a plug-in for execution 2b. Folder watcher with Scenario: Cooperate with the plug-in released by 2a, execute the script in the plug-in, and perform corresponding operations
All in all, the effect of 1 and 2a+2b is the same. One is python script and the other is plugin. You can choose different execution methods according to personal needs.
Essential software:
- Pixyz Scenario Processor
- Pixyz Studio: for creating and publishing plugins
- Remember to install and configure the path of python to ensure that you can execute the script through
python xxx.py
on the command line.
Preparation:
- Pull the entire warehouse to local
- Apply for a trial license of Pixyz Scenario Processor,application and activation method,very important! ! !
Python Script
Open 1_Folder watcher, its directory structure is:
│ config.json
│ run.bat
├───scripts
│ sampleScript.py
│ watcher.py
├───_input
└───_output
Run run.bat
to execute sampleScript.py
to process the model
The running logic of the file:
run.bat
is a batch script, double-click to runrun.bat
, open it with an editor and you can see:
python scripts/watcher.py config.json
pause
The python command is called, the 0th parameter entered is the scriptscripts/watcher.py
, run the watcher.py script in the scripts directory, the 1st parameter entered is config.json , a configuration file
If python is not configured as the default environment of the system, you can also enter python scripts/watcher.py config.json
in the command line (cmd, powershell) that can run python to execute the code
config.json
: Configuration file, open it to see its configuration
{
"input_folder": "_input/",
"output_folder": "_output/",
"optimization": "True",
"extensions":
[
".pxz",
".fbx",
".glb",
".pdf",
".usdz",
".obj",
".3dxml"
]
}
- The input folder is:
_input/
- The output folder is:
_output/
- optimization is True, indicating the need for optimization
- extensions indicates that the accepted model types are:
.pxz
,.fbx
,.glb
,.pdf
,.usdz
,.obj
,.3dxml
, if there are additional required types, you can add them here and delete them if they are not needed. Type
watcher.py
: Monitor the folder, and the endless loop of while(1) will be executed as long as there is a file in the_input
folder and has been copied (isCopyFinished)executeScenarioProcessor
import sys
import json
import os
import subprocess
from ctypes import windll
def main(config_file):
# 读取输入参数
# 输入文件夹 input_folder、输出文件 output_folder、后缀 extensions、是否要优化 optimization
input_folder, output_folder, extensions, optimization = read_config(config_file)
waiting = False
while(1):
# 从 input_folder 中读取文件文件 input_files
input_files = [file for file in os.listdir(input_folder)\
if (os.path.isfile(input_folder + '/' + file)\
and getFileExtension(file) not in ['.xml', ''])]
# 如果 input_folder 中没有文件了,就不处理,继续等待
if len(input_files) == 0:
if not waiting:
print('\n')
print('Waiting for files to process...\n')
waiting = True
continue
# 如果 input_folder 有文件,但是还没有复制完,不处理,继续等待
elif not isCopyFinished(input_folder + '/' + input_files[0]):
continue
# 如果 input_folder 有文件,且已经复制完了,停止等待,开始处理文件
else:
waiting = False
# 目前正在处理的文件 input_file 的路径:输入文件夹路径 + 第0个文件名
input_file = input_folder + '/' + input_files[0]
# 开始处理文件
executeScenarioProcessor(input_file, output_folder, extensions, optimization)
# 处理完文件后,把文件删除,否则会一直处理这个文件,真的陷入死循环了
os.remove(input_file)
def read_config(config_file):
# 打开配置文件 config.json,加载内容到 inputs
with open(config_file) as config:
inputs = json.load(config)
# 读取输入文件夹 "input_folder" 对应的字段
input_folder = inputs['input_folder']
input_folder = os.path.abspath(input_folder)
# 读取输出文件夹 "output_folder" 对应的字段
output_folder = inputs['output_folder']
output_folder = os.path.abspath(output_folder)
# 读取输出后缀 "extensions" 对应的字段
extensions = inputs['extensions']
# 读取输出优化选项 "optimization" 对应的字段
optimization = inputs['optimization']
print('\n')
print('Input folder: %s\n' % input_folder)
print('Output folder: %s\n' % output_folder)
print('Extensions: %s\n' % ' '.join(extensions))
print('Optimization: %s\n' % optimization)
return input_folder, output_folder, extensions, optimization
# 判断文件是否复制完成的函数
def isCopyFinished(inputFile):
"""
Check if the file that was dropped in the input folder has finished being copied
"""
GENERIC_WRITE = 1 << 30
FILE_SHARE_READ = 0x00000001
OPEN_EXISTING = 3
FILE_ATTRIBUTE_NORMAL = 0x80
handle = windll.Kernel32.CreateFileW(inputFile, GENERIC_WRITE, FILE_SHARE_READ, None, OPEN_EXISTING,\
FILE_ATTRIBUTE_NORMAL, None)
if handle != -1:
windll.Kernel32.CloseHandle(handle)
return True
return False
# 获取文件的后缀
def getFileExtension(file):
return os.path.splitext(file)[1]
# 用 PiXYZScenarioProcessor 执行命令,核心步骤
def executeScenarioProcessor(input_file, output_folder, extensions, optimization):
# 注意:把 PiXYZScenarioProcessor.exe 的路径换成自己本地的路径
args = ['C:\Program Files\PiXYZScenarioProcessor\PiXYZScenarioProcessor.exe', 'scripts/sampleScript.py', input_file, output_folder, str(extensions), str(optimization)]
print(sys.argv[0])
print(args)
# 启动子进程运行指令
p = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
while p.poll() is None:
l = str(p.stdout.readline().rstrip()) # This blocks until it receives a newline.
print(l)
print(p.stdout.read())
if __name__ == "__main__":
main(sys.argv[1])
If you do not need to monitor the input folder in real time and delete the models in the input folder, you just need to convert all the models in the input into the output folder. The above code can be simplified as:
import sys
import json
import os
import subprocess
def main(config_file):
input_folder, output_folder = read_config(config_file)
input_files = [file for file in os.listdir(input_folder)\
if (os.path.isfile(input_folder + '/' + file)\
and getFileExtension(file) not in ['.xml', ''])]
print(input_files)
if len(input_files)>0:
for i in range(len(input_files)):
print("-"*30, i, input_files[i], "-"*30)
input_file = input_folder + '/' + input_files[i]
executeScenarioProcessor(input_file, output_folder)
def read_config(config_file):
with open(config_file) as config:
inputs = json.load(config)
input_folder = inputs['input_folder']
input_folder = os.path.abspath(input_folder)
output_folder = inputs['output_folder']
output_folder = os.path.abspath(output_folder)
print('\n')
print('Input folder: %s\n' % input_folder)
print('Output folder: %s\n' % output_folder)
return input_folder, output_folder
def getFileExtension(file):
return os.path.splitext(file)[1]
def try_decode_bytes(b):
for encoding in ['utf-8', 'gbk']:
try:
return b.decode(encoding, errors="ignore")
except Exception as e:
pass
return '{}'.format(b)
def executeScenarioProcessor(input_file, output_folder, extensions, optimization):
args = ['C:\Program Files\PiXYZScenarioProcessor\PiXYZScenarioProcessor.exe', 'scripts/sampleScript.py', input_file, output_folder, str(extensions), str(optimization)]
print(sys.argv[0])
print(args)
p = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
while p.poll() is None:
l = str(p.stdout.readline().rstrip()) # This blocks until it receives a newline.
print(l)
print(p.stdout.read())
if __name__ == "__main__":
main(sys.argv[1])
sampleScript.py
:
import os
import time
import sys
def convertFile(inputFile, outputFolder, extensions, optimization):
fileName = getFileNameWithoutExtension(inputFile)
# Set log file in output folder for debug purposes (otherwise it will be overwritten by next 3d file processing)
core.setLogFile(outputFolder + '/' + fileName + '.log')
# Imports and prepares the input_files using the automatic "Guided import" process (see documentation)
roots = process.guidedImport([inputFile], pxz.process.CoordinateSystemOptions(["automaticOrientation",0], ["automaticScale",0], False, True), ["usePreset",2], pxz.process.ImportOptions(False, True, True), False, False, False, False, False, False)
if optimization:# Comment or delete the lines not necessary to your workflow, adjust parameters' values if necessary
t0, n_triangles, n_vertices, n_parts = getStats(roots[0])
# Automatically selects and deletes parts in the scene, whose size is lower than a maximum size defined in by the parameter Size (in millimeters)
SIZE = 20
scene.selectByMaximumSize(roots, SIZE, -1, False)
scene.deleteSelection()
# Removes through holes from CAD models whose diameter is below the defined diameter
DIAMETER = 10
algo.removeHoles(roots, True, False, False, DIAMETER, 0)
# Deletes patches borders (black lines delimiting CAD models' faces) to allow decimation (run in the next step) to be more efficient. Otherwise, patches borders are considered as important information to preserve.
algo.deletePatches(roots, True)
# Reduces meshes density (triangles count) by decimating them, using the "Decimate To Quality" function (see documentation). The values used here are meant to decimate meshes just enough to lower the triangles count without affecting the visual quality too much, especially on CAD models.
algo.decimate(roots, 1, 0.1, 3, -1, False)
# Removes triangles not visible from a set of cameras automatically placed around the model.
algo.hiddenRemoval(roots, 2, 1024, 16, 90, False, 1)
t1, _n_triangles, _n_vertices, _n_parts = getStats(roots[0])
# Export files
for extension in extensions:
io.exportScene(outputFolder + '/' + fileName + extension)
if optimization:
printStats(fileName, t1 - t0, n_triangles, _n_triangles, n_vertices, _n_vertices, n_parts, _n_parts)
def getFileNameWithoutExtension(file):
return os.path.splitext(os.path.basename(file))[0]
def getStats(root):
core.configureInterfaceLogger(False, False, False) # hide next lines from logs
t = time.time()
n_triangles = scene.getPolygonCount([root], True, False, False)
n_vertices = scene.getVertexCount([root], False, False, False)
n_parts = len(scene.getPartOccurrences(root))
core.configureInterfaceLogger(True, True, True) # reenable logs
return t, n_triangles, n_vertices, n_parts
def printStats(fileName, t, n_triangles, _n_triangles, n_vertices, _n_vertices, n_parts, _n_parts):
print('\n')
print('{:<20s}{:<3s}\n'.format('file ', fileName))
print('{:<20s}{:<8.3f}{:<3s}\n'.format('optimization ', t, ' s'))
print('{:<20s}{:<3s}\n'.format('triangles ', str(n_triangles) + ' -> ' + str(_n_triangles)))
print('{:<20s}{:<3s}\n'.format('vertices ', str(n_vertices) + ' -> ' + str(_n_vertices)))
print('{:<20s}{:<3s}\n'.format('parts ', str(n_parts) + ' -> ' + str(_n_parts)))
# Get arguments passed in command line and call main function
if __name__ == "__main__":
convertFile(sys.argv[1], sys.argv[2], eval(sys.argv[3]), eval(sys.argv[4]))
Interpretation:
In the function of watcher.py
, the input parameters are encapsulated: executeScenarioProcessor
args = ['C:\Program Files\PiXYZScenarioProcessor\PiXYZScenarioProcessor.exe', 'scripts/sampleScript.py', input_file, output_folder, str(extensions), str(optimization)]
corresponds to argv in sampleScript.py
respectively:
if __name__ == "__main__":
convertFile(sys.argv[1], sys.argv[2], eval(sys.argv[3]), eval(sys.argv[4]))
Right now:
- sys.argv[1] = input_file
- sys.argv[2] = output_folder
- eval(sys.argv[3]) = extensions
- eval(sys.argv[4])) = optimization
Use guidedImport to import inputFile and return it to roots.
# Imports and prepares the input_files using the automatic "Guided import" process (see documentation)
roots = process.guidedImport([inputFile], pxz.process.CoordinateSystemOptions(["automaticOrientation",0], ["automaticScale",0], False, True), ["usePreset",2], pxz.process.ImportOptions(False, True, True), False, False, False, False, False, False)
After that, there is a series of operations on the roots, such as counting the number of triangular patches, the number of vertices, and the number of parts under the roots (maybe parts can be translated into components?)
t0, n_triangles, n_vertices, n_parts = getStats(roots[0])
def getStats(root):
core.configureInterfaceLogger(False, False, False) # hide next lines from logs
t = time.time()
n_triangles = scene.getPolygonCount([root], True, False, False)
n_vertices = scene.getVertexCount([root], False, False, False)
n_parts = len(scene.getPartOccurrences(root))
core.configureInterfaceLogger(True, True, True) # reenable logs
return t, n_triangles, n_vertices, n_parts
Select and delete certain parts
SIZE = 20
scene.selectByMaximumSize(roots, SIZE, -1, False)
scene.deleteSelection()
For others, you can refer to the official Python api and the samples that come with the studio
Plugin
The script published as a plug-in is the same script as the above, but it requires two steps:
- Open 2a_Scenario sample, publish the
sampleScript.py
plug-in, and put the plug-in in thePiXYZScenarioProcessor\plugins
directory - Open 2b_Folder watcher with Scenario, use
run.bat
to executewatcher.py
, run the plug-in
The directory structure of 2a_Scenario sample is as follows:
│ publish.bat
│ sample.pxzext
└───sample
│ plugin.xml
└───scripts
sampleScript.py
sampleScript.py
The biggest difference from 1_Folder watcher is that there is no execution code:
# Get arguments passed in command line and call main function
if __name__ == "__main__":
convertFile(sys.argv[1], sys.argv[2], eval(sys.argv[3]), eval(sys.argv[4]))
plugin.xml
The function to be executed is specified in asconvertFile
, and the input parameters are:
<?xml version="1.0" encoding="utf-8" ?>
<module name="sample" version="2021.1.1.5">
<include module="Core"/>
<include module="IO"/>
<function name="convertFile" state="Stable" scriptable="true" guiable="true" description="" logName="" scriptFile="sampleScript.py">
<parameters>
<parameter name="inputFile" type="ImportFilePath" state="Stable"/>
<parameter name="outputFolder" type="OutputDirectoryPath" state="Stable"/>
<parameter name="extensions" type="StringList" state="Stable"/>
<parameter name="optimization" type="Bool" state="Stable"/>
</parameters>
</function>
</module>
publish.bat
specifies the use ofPiXYZStudio
'sPiXYZStudioPublishPlugin
to publish the plug-in (note: you need to usePiXYZStudio
's < /span>)PiXYZStudioPublishPlugin.exe
instead ofPiXYZScenarioProcessor.exe
The compiled folder issample
, the compiled plug-in name is sample.pxzext
, and then sample.pxzext
is copied to PiXYZScenarioProcessor
in the directory plugins
in the directory
Change the paths of PiXYZStudio
and PiXYZScenarioProcessor
to your own local ones. You can also execute them one by one, and then manually put the compiled sample.pxzext into PiXYZScenarioProcessor\plugins
below
rem Compile plugin sources
"C:\Program Files\PiXYZStudio\PiXYZStudioPublishPlugin.exe" "%cd%\sample" "%cd%\sample.pxzext"
rem Copy scenario plugin in SP installation folder
copy "%cd%\sample.pxzext" "C:\ProgramData\PiXYZScenarioProcessor\plugins\"
pause
After the compilation is completed, place sample.pxzext under PiXYZScenarioProcessor\plugins
and you can open 2b_Folder watcher with Scenario
The directory structure of 2b_Folder watcher with Scenario is:
│ config.json
│ run.bat
├───scripts
│ watcher.py
├───_input
└───_output
The difference between and 1 is under scripts sampleScript.py
now becomes PiXYZScenarioProcessor\plugins\sample.pxzext
watcher.py
The biggest difference from 1 is that the input parameters args have changed:
1_Folder watcher\scripts\watcher.py
args = ['C:\Program Files\PiXYZScenarioProcessor\PiXYZScenarioProcessor.exe', 'scripts/sampleScript.py', input_file, output_folder, str(extensions), str(optimization)]
2b_Folder watcher with Scenario\scripts\watcher.py
args = ['C:\Program Files\PiXYZScenarioProcessor\PiXYZScenarioProcessor.exe', 'sample', 'convertFile', "\"" + input_file.replace("\\", "\\\\") + "\"", "\"" + output_folder.replace("\\", "\\\\") + "\"", str(extensions), "\"" + str(optimization) + "\""]
Input sample, convertFile and and specified in plugin.xml
correspond to module
function