// * Copyright (c) 2007-$CurrentYear$, Linden Research, Inc. // * $License$ global string $IMPORTPRIMSCRIPT_COMMAND; $IMPORTPRIMSCRIPT_COMMAND = ""; global string $IMAGEMAGIK_MONTAGE_COMMAND; $IMAGEMAGICK_MONTAGE_COMMAND = ""; // ====================================== // set the command for montage here. // for a fink installation on macosx, use this: $IMAGEMAGIK_MONTAGE_COMMAND = "/sw/bin/montage"; // for a cygwin installation on windows, use this: // $IMAGEMAGIK_MONTAGE_COMMAND = "C:\cygwin\bin\montage.exe; // set the importprimscript command here. // $IMPORTPRIMSCRIPT_COMMAND = "C:\importprimscript\importprimscript.exe"; // =========================== // CHANGELOG // v1.03 Thu Nov 1 14:23:39 PDT 2007 // - gui updated // // v1.04 Fri Jan 11 05:01:41 PST 2008 // - fixed transforms // - fixed too small/big errors // - implemented surface texture atlasing // - added topology detection // - cleaned up filenames (avoid "|" in filenames, please.) global string $llPrimScript; global proc string llFirst(string $list[]) { return $list[0]; } global proc string llGetShadingGroup(string $object) { string $allNodes[] = (`listHistory -f true $object` ); string $node = ""; for ($node in $allNodes) { if(`nodeType $node` == "shadingEngine") { return $node; } } return ""; } global proc llSystem(string $command) { print("Running command: " + $command + "\n\n"); string $output = system($command); print($output); } global proc string llBaseFileName(string $file_name) { string $tokens[]; tokenize($file_name, "/", $tokens); string $base_name = $tokens[size($tokens)-1]; return $base_name; } global proc vector llSculptGetAtlasSize(int $count) { // compute the number of tiles in x and y for an ideal atlas // ideal = (1) enough room for all tiles, (2) each size is a power of 2 // and (3) the atlas is as square as possible int $x = 1; int $y = 1; while ($x * $y < $count) { if ($x == $y) $x *= 2; else $y = $x; } vector $value = << $x, $y, 0 >>; return $value; } global proc vector llSculptGetAtlasPosition(int $size_x, int $size_y, int $index) { int $position_y = $index / $size_x; int $position_x = $index - $size_x * $position_y; vector $position = << $position_x, $position_y, 0 >>; return $position; } global proc llSculptMakeAtlas(string $atlas_file_name, string $objects[], string $file_base, string $file_extension, int $surface_resolution_x, int $surface_resolution_y, int $tile_x, int $tile_y) { global string $IMAGEMAGIK_MONTAGE_COMMAND; // construct the ImageMagick command to construct the atlas: string $command; $command = $IMAGEMAGIK_MONTAGE_COMMAND; $command += " -background black"; $command += " -tile " + $tile_x + "x" + $tile_y; $command += " -geometry " + $surface_resolution_x + "x" + $surface_resolution_y; string $object; for ($object in $objects) { $command += " " + $file_base + "-" + $object + "-surface." + $file_extension; } $command += " " + $atlas_file_name + "." + $file_extension; llSystem($command); } global proc string llCleanFilename(string $filename) { string $clean = $filename; $clean = substituteAllString($clean, "|", "+"); return $clean; } global proc float llDistance3Float(float $p1[], float $p2[]) { float $distance = sqrt(pow($p1[0] - $p2[0], 2) + pow($p1[1] - $p2[1], 2) + pow($p1[2] - $p2[2], 2)); return $distance; } global proc int llTestPole(string $nurb, int $u, int $v) { float $threshold = 0.01; int $samples = 10; float $total_distance = 0; float $last_point[3]; int $i; for ($i = 0; $i < $samples; $i++) { float $p = (float)$i / ($samples - 1); float $sample_u = $u; float $sample_v = $v; if ($u == -1) $sample_u = $p; if ($v == -1) $sample_v = $p; float $point[3] = pointOnSurface("-turnOnPercentage", 1, "-parameterU", $sample_u, "-parameterV", $sample_v, $nurb); if ($i != 0) // not first time through { $total_distance += llDistance3Float($point, $last_point); } $last_point = $point; } return ($total_distance < ($samples * $threshold)); } global proc int llWrapTest(string $nurb, int $direction) { float $threshold = 0.01; int $samples = 10; float $total_distance = 0; int $i; for ($i = 0; $i < $samples; $i++) { float $p = (float)$i / ($samples - 1); float $u1, $u2, $v1, $v2; if ($direction == 0) { $u1 = 0; $u2 = 1; $v1 = $p; $v2 = $p; } if ($direction == 1) { $u1 = $p; $u2 = $p; $v1 = 0; $v2 = 1; } float $point1[3] = pointOnSurface("-turnOnPercentage", 1, "-parameterU", $u1, "-parameterV", $v1, $nurb); float $point2[3] = pointOnSurface("-turnOnPercentage", 1, "-parameterU", $u2, "-parameterV", $v2, $nurb); $total_distance += llDistance3Float($point1, $point2); } return ($total_distance < ($samples * $threshold)); } global proc int llOutwardTest(string $nurb) { float $total_orientation = 0; float $u, $v; for ($u = 0; $u <= 1; $u += 0.1) for ($v = 0; $v <= 1; $v += 0.1) { float $point[] = pointOnSurface("-turnOnPercentage", 1, "-parameterU", $u, "-parameterV", $v, $nurb); float $normal[] = pointOnSurface("-normal", "-turnOnPercentage", 1, "-parameterU", $u, "-parameterV", $v, $nurb); // check the orientation of the normal w/r/t the direction from center float $center_dir[]; for ($i = 0; $i < 3; $i++) $center_dir[$i] = $point[$i] - 0.5; float $orientation = 0; // dot product for ($i = 0; $i < 3; $i++) $orientation += $center_dir[$i] * $normal[$i]; $total_orientation += $orientation; } return ($total_orientation >= 0); } global proc llSculptExport(string $object, string $file_name, string $file_extension, string $file_format, int $resolution_x, int $resolution_y, int $maximize_scale, int $fix_orientation, int $surface_bake, int $surface_resolution_x, int $surface_resolution_y, int $surface_atlas, string $surface_atlas_file_name, int $surface_atlas_x, int $surface_atlas_y, int $surface_atlas_index, int $surface_shadows, int $generate_primscript) { // copy it, because we're going to mutilate it. MUHAHAHAAAaa... string $object_copy = llFirst(duplicate($object)); // disentangle from groups string $parents[] = listRelatives("-parent", $object_copy); if (size($parents) != 0) $object_copy = llFirst(parent("-world", $object_copy)); // reset its transformations: makeIdentity("-apply", 1, "-translate", 1, "-rotate", 1, "-scale", 1, "-normal", 2, $object_copy); // find its position/size float $bounding_min[3]; float $bounding_max[3]; $bounding_min = getAttr($object_copy + ".boundingBoxMin"); $bounding_max = getAttr($object_copy + ".boundingBoxMax"); float $center[3]; for ($i = 0; $i < 3; $i++) $center[$i] = ($bounding_min[$i] + $bounding_max[$i]) / 2.0; float $scale[3]; int $i; for ($i = 0; $i < 3; $i++) { $scale[$i] = $bounding_max[$i] - $bounding_min[$i]; if ($scale[$i] < 0.00001) $scale[$i] = 1; if ($scale[$i] > 10.0) $scale[$i] = 10.0; } float $scale_max = 0; for ($i = 0; $i < 3; $i++) if ($scale[$i] > $scale_max) $scale_max = $scale[$i]; if (!$maximize_scale) { for ($i = 0; $i < 3; $i++) $scale[$i] = $scale_max; } // scale and move it to the unit cube scale("-centerPivot", "-relative", 1/$scale[0], 1/$scale[1], 1/$scale[2], $object_copy); move("-relative", 0.5 - $center[0], 0.5 - $center[1], 0.5 - $center[2], $object_copy); int $topology = 1; float $texture_repeat_x = 1; float $texture_repeat_y = 1; float $texture_rotate = 0; float $texture_offset_x = 0; float $texture_offset_y = 0; // // nurbs surfaces can be adjusted to ensure correct orientation // if ($fix_orientation) { string $shape = llFirst(listRelatives("-shapes", $object_copy)); if ((nodeType($object_copy) == "nurbsSurface") || (($shape != "") && (nodeType($shape) == "nurbsSurface"))) { int $swap_uv = 0; int $north_pole = llTestPole($object_copy, -1, 0); int $south_pole = llTestPole($object_copy, -1, 1); int $east_pole = llTestPole($object_copy, 0, -0); int $west_pole = llTestPole($object_copy, 1, -1); int $latitude_wrap = llWrapTest($object_copy, 0); int $longitude_wrap = llWrapTest($object_copy, 1); if (($latitude_wrap) && ($longitude_wrap)) { $topology = 2; // torus } else if (($north_pole) && ($south_pole) && ($latitude_wrap)) { $topology = 1; // sphere } else if (($east_pole) && ($west_pole) && ($longitude_wrap)) { $topology = 1; // sphere $swap_uv = 1; } else if ($latitude_wrap) { $topology = 4; // cylinder } else if ($longitude_wrap) { $topology = 4; // cylinder $swap_uv = 1; } else { $topology = 3; // plane } if ($swap_uv) { print("swapping UVs for " + $object + "\n"); reverseSurface("-direction", 3, $object_copy); // swap uv reverseSurface("-direction", 1, $object_copy); // reverse v to keep orienation $texture_rotate = 3.1415926 / 2.0; $texture_repeat_x *= -1.0; $texture_repeat_y *= -1.0; } if (!llOutwardTest($object_copy) && ($topology != 3)) // need to invert { print("reversing direction for " + $object + "\n"); reverseSurface("-direction", 1, $object_copy); } } else { warning("cannot fix orientation on non-nurbs object: " + $object); } } // create temporary shading network string $sampler_info = createNode("samplerInfo"); string $full_file_name = $file_name + "." + $file_extension; print("exporting sculpt map for " + $object + " into file " + $full_file_name + "\n"); // bake sculpt texture string $fileNodes[] = convertSolidTx("-fileImageName", $full_file_name, "-fileFormat", $file_format, "-force", 1, "-resolutionX", $resolution_x, "-resolutionY", $resolution_y, $sampler_info + ".pointWorld", $object_copy); delete($fileNodes); // we don't want 'em. why do you make 'em? delete($sampler_info); delete($object_copy); // // bake texture also? // if ($surface_bake) { string $texture_file_name = $file_name + "-surface." + $file_extension; string $shading_group = llGetShadingGroup($object); print("baking texture for " + $object + " into file " + $texture_file_name + "\n"); // bake sculpt texture string $fileNodes[] = convertSolidTx("-fileImageName", $texture_file_name, "-fileFormat", $file_format, "-force", 1, "-resolutionX", $surface_resolution_x, "-resolutionY", $surface_resolution_y, "-shadows", $surface_shadows, $shading_group, $object); delete($fileNodes); // still don't want 'em. } if ($surface_atlas) { $texture_repeat_x /= $surface_atlas_x; $texture_repeat_y /= $surface_atlas_y; vector $atlas_position = llSculptGetAtlasPosition($surface_atlas_x, $surface_atlas_y, $surface_atlas_index); $texture_offset_x = (($atlas_position.x + 0.5) / $surface_atlas_x - 0.5); $texture_offset_y = -(($atlas_position.y + 0.5) / $surface_atlas_y - 0.5); } // // construct primScript // if ($generate_primscript) { global string $llPrimScript; // size bounds check for ($i = 0; $i < 3; $i++) { if ($scale[$i] < 0.01) { warning($object + " is too small."); $scale[$i] = 0.01; } if ($scale[$i] > 10.0) { warning($object + " is too big."); $scale[$i] = 10.0; } } // strip dir from filename string $base_name = llBaseFileName($file_name); string $base_atlas_name = llBaseFileName($surface_atlas_file_name); $llPrimScript += "newPrim\n"; $llPrimScript += "prim -setObjectName " + $object + " -deleteScript\n"; $llPrimScript += "shape -setSculpt " + $base_name + " " + $topology + " -deleteScript\n"; $llPrimScript += "texture"; if ($surface_bake) { if ($surface_atlas) $llPrimScript += " -setTexture " + $base_atlas_name + " -1"; else $llPrimScript += " -setTexture " + $base_name + "-surface -1"; $llPrimScript += " -setTexturePos " + $texture_offset_x + " " + $texture_offset_y + " " + $texture_rotate + " " + $texture_repeat_x + " " + $texture_repeat_y + " "; } $llPrimScript += " -deleteScript\n"; $llPrimScript += "transform -setScale " + $scale[0] + " " + $scale[1] + " " +$scale[2] + " -gotoRelativePos " + $center[0] + " " + $center[1] + " " +$center[2] + " -deleteScript \n"; // sanity check: // spaceLocator("-position", $center[0]*$scale[0], $center[1]*$scale[1], $center[2]*$scale[2]); } } global proc int llSculptEditorCallback() { string $objects[] = ls("-sl"); if (size($objects) == 0) { warning("please select objects to export"); return 0; } string $filename = textFieldButtonGrp("-query", "-fileName", "llSculptEditorFilename"); int $resolution_x = intSliderGrp("-query", "-value", "llSculptEditorResolutionX"); int $resolution_y = intSliderGrp("-query", "-value", "llSculptEditorResolutionY"); int $fix_orientation = checkBoxGrp("-query", "-value1", "llSculptEditorFixOrientation"); int $maximize_scale = checkBoxGrp("-query", "-value1", "llSculptEditorMaximizeScale"); int $surface_bake = checkBoxGrp("-query", "-value1", "llSculptEditorSurfaceBake"); int $surface_resolution_x = intSliderGrp("-query", "-value", "llSculptEditorSurfaceResolutionX"); int $surface_resolution_y = intSliderGrp("-query", "-value", "llSculptEditorSurfaceResolutionY"); int $surface_atlas = checkBoxGrp("-query", "-value1", "llSculptEditorSurfaceAtlas"); int $surface_shadows = checkBoxGrp("-query", "-value1", "llSculptEditorSurfaceShadows"); int $generate_primscript = checkBoxGrp("-query", "-value1", "llSculptEditorGeneratePrimScript"); int $sl_upload = checkBoxGrp("-query", "-value1", "llSculptEditorSLUpload"); string $sl_firstname = textFieldGrp("-query", "-text", "llSculptEditorSLFirstName"); string $sl_lastname = textFieldGrp("-query", "-text", "llSculptEditorSLLastName"); string $sl_password = textFieldGrp("-query", "-text", "llSculptEditorSLPassword"); string $sl_region = textFieldGrp("-query", "-text", "llSculptEditorSLRegion"); int $sl_position_x = floatFieldGrp("-query", "-value1", "llSculptEditorSLPosition"); int $sl_position_y = floatFieldGrp("-query", "-value2", "llSculptEditorSLPosition"); int $sl_position_z = floatFieldGrp("-query", "-value3", "llSculptEditorSLPosition"); // get filetype string $file_type; string $file_base; string $file_extension; string $tokens[]; tokenize($filename, ".", $tokens); if (size($tokens) == 1) // no extension, default to bmp { $file_base = $filename; $file_type = "bmp"; $file_extension = "bmp"; } else { $file_extension = $tokens[size($tokens) - 1]; int $i; for ($i = 0; $i < size($tokens) - 1; $i++) { $file_base += $tokens[$i]; if ($i != size($tokens) - 2) $file_base += "."; } if ($file_extension == "bmp") $file_type = "bmp"; else if (($file_extension == "jpg") || ($file_extension == "jpeg")) $file_type = "jpg"; else if (($file_extension == "tif") || ($file_extension == "tiff")) $file_type = "tif"; else if ($file_extension == "tga") $file_type = "tga"; else { warning("unknown image type (" + $file_extension + "). switching to bmp"); $file_type = "bmp"; $file_extension = "bmp"; } } global string $llPrimScript; $llPrimScript = ""; int $multi_file = 0; if (size($objects) > 1) $multi_file = 1; if ($generate_primscript) $multi_file = 1; vector $atlas_size = llSculptGetAtlasSize(size($objects)); string $atlas_file_name = $file_base + "-atlas"; int $object_number; for ($object_number = 0; $object_number < size($objects); $object_number++) { string $object = $objects[$object_number]; string $this_filename = $file_base; if ($multi_file) { string $clean_object_name = llCleanFilename($object); $this_filename += "-" + $clean_object_name; } llSculptExport($object, $this_filename, $file_extension, $file_type, $resolution_x, $resolution_y, $maximize_scale, $fix_orientation, $surface_bake, $surface_resolution_x, $surface_resolution_y, $surface_atlas, $atlas_file_name, $atlas_size.x, $atlas_size.y, $object_number, $surface_shadows, $generate_primscript); } if ($surface_atlas) { llSculptMakeAtlas($atlas_file_name, $objects, $file_base, $file_extension, $surface_resolution_x, $surface_resolution_y, $atlas_size.x, $atlas_size.y); } if ($generate_primscript) { int $file_id=fopen($file_base + ".primscript", "w"); fwrite($file_id, $llPrimScript); fclose($file_id); } if ($sl_upload) { global string $IMPORTPRIMSCRIPT_COMMAND; string $command = $IMPORTPRIMSCRIPT_COMMAND; $command += " " + $sl_firstname; $command += " " + $sl_lastname; $command += " " + $sl_password; $command += " " + $sl_region; $command += " " + $sl_position_x + " " + $sl_position_y + " " + $sl_position_z; $command += " " + $file_base + ".primscript"; llSystem($command); } select($objects); return 1; } global proc llSculptEditorSetFilenameCallback(string $filename, string $filetype) { textFieldButtonGrp("-edit", "-fileName", $filename, "llSculptEditorFilename"); } global proc llSculptEditorBrowseCallback() { fileBrowser("llSculptEditorSetFilenameCallback", "Export", "image", 1); } global proc llSculptEditorSurfaceBakeCallback() { int $surface_bake = checkBoxGrp("-query", "-value1", "llSculptEditorSurfaceBake"); control("-edit", "-enable", $surface_bake, "llSculptEditorSurfaceResolutionX"); control("-edit", "-enable", $surface_bake, "llSculptEditorSurfaceResolutionY"); control("-edit", "-enable", $surface_bake, "llSculptEditorSurfaceShadows"); control("-edit", "-enable", $surface_bake, "llSculptEditorSurfaceAtlas"); } global proc llSculptEditorSLUploadCallback() { int $sl_upload = checkBoxGrp("-query", "-value1", "llSculptEditorSLUpload"); control("-edit", "-enable", $sl_upload, "llSculptEditorSLFirstName"); control("-edit", "-enable", $sl_upload, "llSculptEditorSLLastName"); control("-edit", "-enable", $sl_upload, "llSculptEditorSLRegion"); control("-edit", "-enable", $sl_upload, "llSculptEditorSLPosition"); control("-edit", "-enable", $sl_upload, "llSculptEditorSLPassword"); } global proc llSculptEditor() { string $commandName = "llSculptExport"; string $layout = getOptionBox(); setParent $layout; setOptionBoxCommandName($commandName); setUITemplate -pushTemplate DefaultTemplate; scrollLayout -childResizable 1 -defineTemplate DefaultTemplate; tabLayout -tabsVisible 0 -scrollable 1; string $parent = `columnLayout -adjustableColumn 1`; separator -height 10 -style "none"; textFieldButtonGrp -label "Filename" -fileName "sculpt.bmp" -buttonLabel "Browse" -buttonCommand "llSculptEditorBrowseCallback" llSculptEditorFilename; intSliderGrp -field on -label "X Resolution" -minValue 1 -maxValue 512 -fieldMinValue 1 -fieldMaxValue 4096 -value 64 llSculptEditorResolutionX; intSliderGrp -field on -label "Y Resolution" -minValue 1 -maxValue 512 -fieldMinValue 1 -fieldMaxValue 4096 -value 64 llSculptEditorResolutionY; checkBoxGrp -label "" -label1 "Generate primscript" -numberOfCheckBoxes 1 -value1 on llSculptEditorGeneratePrimScript; checkBoxGrp -label "" -label1 "Maximize scale" -numberOfCheckBoxes 1 -value1 on llSculptEditorMaximizeScale; checkBoxGrp -label "" -label1 "Correct orientation" -numberOfCheckBoxes 1 -value1 on llSculptEditorFixOrientation; separator; checkBoxGrp -label "" -label1 "Bake surface texture" -numberOfCheckBoxes 1 -value1 off -changeCommand llSculptEditorSurfaceBakeCallback llSculptEditorSurfaceBake; intSliderGrp -field on -label "X Resolution" -minValue 1 -maxValue 512 -fieldMinValue 1 -fieldMaxValue 4096 -value 128 llSculptEditorSurfaceResolutionX; intSliderGrp -field on -label "Y Resolution" -minValue 1 -maxValue 512 -fieldMinValue 1 -fieldMaxValue 4096 -value 128 llSculptEditorSurfaceResolutionY; int $atlas_on = 0; global string $IMAGEMAGICK_MONTAGE_COMMAND; if ($IMAGEMAGICK_MONTAGE_COMMAND != "") $atlas_on = 1; checkBoxGrp -label "" -label1 "Atlas textures" -numberOfCheckBoxes 1 -value1 $atlas_on llSculptEditorSurfaceAtlas; checkBoxGrp -label "" -label1 "Include shadows" -numberOfCheckBoxes 1 -value1 off llSculptEditorSurfaceShadows; separator; int $upload = 0; global string $IMPORTPRIMSCRIPT_COMMAND; if ($IMPORTPRIMSCRIPT_COMMAND != "") $upload = 1; checkBoxGrp -label "" -label1 "Upload to Second Life" -numberOfCheckBoxes 1 -value1 off -enable $upload -changeCommand llSculptEditorSLUploadCallback llSculptEditorSLUpload; textFieldGrp -label "First Name" llSculptEditorSLFirstName; textFieldGrp -label "Last Name" llSculptEditorSLLastName; textFieldGrp -label "Password" llSculptEditorSLPassword; textFieldGrp -label "Region (sim)" llSculptEditorSLRegion; floatFieldGrp -numberOfFields 3 -label "Position" -extraLabel "m" -value1 128.0 -value2 128.0 -value3 50.0 llSculptEditorSLPosition; setUITemplate -popTemplate; string $applyBtn = getOptionBoxApplyBtn(); button -edit -label "Export" -command "llSculptEditorCallback" $applyBtn; string $applyAndCloseBtn = getOptionBoxApplyAndCloseBtn(); button -edit -label "Export and Close" -command "llSculptEditorCallback" $applyAndCloseBtn; setOptionBoxTitle("Maya Sculptie Exporter v1.03"); setOptionBoxHelpTag( "ConvertFileText" ); llSculptEditorSurfaceBakeCallback(); llSculptEditorSLUploadCallback(); showOptionBox(); } // llSculptEditor;