All Activity
- Past hour
-
- Today
-
Understood, I have to redo the rig from scratch using this TSM2 and version 15 if I remember right. You also used an "auto assign" at one point I think that made things quicker and that's why I could use the video of that LAT to study more closely but I don't think it's available yet. I'll keep checking to see when it is. thanks for responding robcat.
-
-
Looks good, now we just need a cigarette dangling out the corner of the mouth.
-
- Yesterday
-
problem 1 - the only bone you should be rotating in a smartskin window is the bone that smartskin is for. The smartskin window says it is for "LBicepDynamic", so moving "LBicep" is going to be trouble unless there is a great reason to do it. problem 2- you're still using this rig that has a zillion extra bones and constraints added on top of the geometry bones. When you are making smartskin you should be using a version of the model that is geometry bones only... geometry bones that are easy to move. problem 3 - I don't think this "COGS" rig was designed with smartskin in mind. Is there a place in the instructions where it says "do your smartskins now"? If there is, I don't think this stage of the model is it. problem 4 - You should be using TSM2. You'd be done already. TSM2 gives you a version of your model with just geometry bones for you to do your CP weighting and smartskinning and no constraints yet making things fly around crazy.
-
svetlik started following SmartSkin Weirdness
-
Is this just on my machine er whut? Please try to rotate the Lbicep bone in the Smartskin window, either with the manipulator or scrubbing the Y axis in the PWS. On my machine it goes WACKO rotating spastically in large increments and the bone is way off. Even tried saving the model to disk & importing it into a new Prj. but no difference. Recently had similart problems but was resolved with AVG anti virus but it's not working now, so any help appreciated. SuitCoat3.prj
-
Two more slight variations Rocky44_000.mp4 @John Bigboote wanted a bigger shape on Ro-cky Rocky44x_000.mp4
- Last week
-
It's spline controllers... see? Rocky42_000.mp4
-
Thanks Robert! He has a basic rig for posing, If I ever do anything more with him he would need some major smart skinning
-
That's a great looking Batman, Michael. Is he rigged, too?
-
Free Harvard CS50 Intro to Computer Science course
robcat2075 replied to Rodney's topic in C++ Learners
Some results from recent C++ exercise regarding file data manipulation. Using this image as input: Here "edge detection" has been done. Every pixel in the original was tested to see if its red, green, or blue values were different from its neighboring pixels beyond a certain threshold. If so, a white pixel was plotted in the new image. If not, a black pixel is plotted. Here the image has been rotated. Every pixel in the output image is populated by the values of a pixel that is x degrees from it, around a center point. If that x degree rotation is looking at a position that is off the original frame, a black pixel is plotted. -
Pizza Time started following Rodney
-
Has anyone ever tried using AM under Crossover, either on Mac or Linux? I'm contemplating trying running it on Linux under Crossover and just wondered if anyone else had already tried this.
-
Ganthofer started following WatchFolder utility
-
The updated Watchfolder script: import os import time import shutil import keyboard import subprocess import re from datetime import datetime import configparser import sys # === DEFAULT CONFIG === DEFAULTS = { "watch_dir": "F:/renderfolder", "ffmpeg_path": "ffmpeg", "framerate": "24", "timeout": "5", "video_basename": "video", "max_runtime_minutes": "0", "reset_timeout_on_video": "false", # GIF options "make_gif": "true", "gif_max_seconds": "6", # 0 = full video "gif_width": "320", "gif_fps": "12", "gif_suffix": "_thumb" } def get_exe_dir(): if getattr(sys, 'frozen', False): return os.path.dirname(sys.executable) else: return os.path.dirname(os.path.abspath(__file__)) INI_FILE = os.path.join(get_exe_dir(), "watchfolder.ini") # ----------------- Config ----------------- def load_config(): config = configparser.ConfigParser(inline_comment_prefixes=("#", ";")) if not os.path.exists(INI_FILE): # write a friendly ini with comment lines (not inline) config["settings"] = DEFAULTS with open(INI_FILE, "w") as f: f.write( "[settings]\n" "watch_dir = F:/renderfolder\n" "ffmpeg_path = ffmpeg\n" "framerate = 24\n" "timeout = 5\n" "video_basename = video\n" "max_runtime_minutes = 0\n" "reset_timeout_on_video = false\n" "# GIF settings\n" "make_gif = true\n" "gif_max_seconds = 6\n" "gif_width = 320\n" "gif_fps = 12\n" "gif_suffix = _thumb\n" ) print(f"[i] Created default {INI_FILE}") else: config.read(INI_FILE) if "settings" not in config: config["settings"] = {} for key, val in DEFAULTS.items(): if key not in config["settings"]: config["settings"][key] = val return config["settings"] # ----------------- Helpers ----------------- def get_png_files(watch_dir): return sorted([f for f in os.listdir(watch_dir) if f.lower().endswith('.png')]) def get_next_video_filename(watch_dir, basename): count = 1 while True: candidate = f"{basename}_{count:04d}.mp4" if not os.path.exists(os.path.join(watch_dir, candidate)): return candidate count += 1 def guess_pattern(filename): match = re.search(r"([^.]+)\.(\d+)\.png$", filename) if match: prefix, digits = match.groups() return f"{prefix}.%0{len(digits)}d.png" return None # ----------------- Core Actions ----------------- def convert_sequence_to_mp4(watch_dir, first_file, ffmpeg_path, framerate, video_basename): """Convert PNG sequence to MP4. Returns absolute path to MP4 on success, else None.""" pattern = guess_pattern(first_file) if not pattern: print(f"[!] Could not determine pattern from {first_file}") return None output_name = get_next_video_filename(watch_dir, video_basename) output_path = os.path.join(watch_dir, output_name) print(f"[+] Converting to MP4: {output_name}") try: subprocess.run([ ffmpeg_path, "-y", "-framerate", str(framerate), "-i", pattern, "-c:v", "libx264", "-pix_fmt", "yuv420p", output_path ], cwd=watch_dir, check=True) print(f"[✓] Video saved as: {output_name}") return output_path except subprocess.CalledProcessError as e: print(f"[!] FFmpeg failed: {e}") return None def move_sequence_to_archive(watch_dir, png_files): now = datetime.now().strftime("%Y%m%d_%H%M%S") archive_dir = os.path.join(watch_dir, "processed", now) os.makedirs(archive_dir, exist_ok=True) for f in png_files: shutil.move(os.path.join(watch_dir, f), os.path.join(archive_dir, f)) print(f"[→] Moved PNGs to: {archive_dir}") return archive_dir def make_gif_thumbnail(video_path, ffmpeg_path, gif_width, gif_fps, gif_max_seconds, gif_suffix): """ Create a small animated GIF from MP4 using palettegen/paletteuse for quality. Saves next to the MP4: e.g., video_0001_thumb.gif """ base, _ = os.path.splitext(video_path) gif_path = f"{base}{gif_suffix}.gif" palette_path = f"{base}_palette.png" # Build common filter chain: fps + scale + split for palette vf_chain = f"fps={gif_fps},scale={gif_width}:-1:flags=lanczos" # Optional duration clamp (0 = full length) duration_args = [] if gif_max_seconds > 0: duration_args = ["-t", str(gif_max_seconds)] try: # 1) Palette generation subprocess.run([ ffmpeg_path, "-y", "-i", video_path, *duration_args, "-vf", f"{vf_chain},palettegen=stats_mode=diff", palette_path ], check=True) # 2) Palette use subprocess.run([ ffmpeg_path, "-y", "-i", video_path, *duration_args, "-i", palette_path, "-lavfi", f"{vf_chain}[x];[x][1:v]paletteuse=dither=bayer:bayer_scale=5", gif_path ], check=True) # Clean up palette try: os.remove(palette_path) except OSError: pass print(f"[✓] GIF thumbnail created: {os.path.basename(gif_path)}") return gif_path except subprocess.CalledProcessError as e: print(f"[!] GIF creation failed: {e}") return None # ----------------- Monitor Loop ----------------- def monitor(settings): watch_dir = settings["watch_dir"] ffmpeg_path = settings["ffmpeg_path"] framerate = int(settings.get("framerate", 24)) timeout = int(settings.get("timeout", 5)) video_basename = settings["video_basename"] max_runtime = int(settings.get("max_runtime_minutes", "0").strip()) * 60 reset_on_video = settings.get("reset_timeout_on_video", "false").lower() == "true" make_gif = settings.get("make_gif", "true").lower() == "true" gif_max_seconds = int(settings.get("gif_max_seconds", "6").strip()) gif_width = int(settings.get("gif_width", "320").strip()) gif_fps = int(settings.get("gif_fps", "12").strip()) gif_suffix = settings.get("gif_suffix", "_thumb") print(f"👁️ Monitoring folder: {watch_dir}") print(f"[i] FFmpeg: {ffmpeg_path}, timeout: {timeout}s, framerate: {framerate}fps") if max_runtime > 0: print(f"[i] Will auto-exit after {max_runtime // 60} minutes (unless reset)") if make_gif: print(f"[i] GIF: width={gif_width}, fps={gif_fps}, max_seconds={gif_max_seconds} (suffix='{gif_suffix}')") start_time = time.time() previous_files = set(get_png_files(watch_dir)) last_change_time = time.time() while True: # Escape to exit if keyboard.is_pressed("esc"): print("[✋] Escape key pressed. Exiting.") break time.sleep(1) # Auto-exit if timer exceeded if max_runtime > 0 and (time.time() - start_time > max_runtime): print("[!] Max runtime reached. Exiting.") break current_files = set(get_png_files(watch_dir)) if current_files != previous_files: previous_files = current_files last_change_time = time.time() continue if current_files and (time.time() - last_change_time > timeout): png_files = sorted(current_files) print(f"[⏳] Sequence complete: {len(png_files)} files") # 1) Make MP4 mp4_path = convert_sequence_to_mp4(watch_dir, png_files[0], ffmpeg_path, framerate, video_basename) # 2) Move PNGs move_sequence_to_archive(watch_dir, png_files) # 3) Make GIF thumbnail (optional) if mp4_path and make_gif: make_gif_thumbnail( video_path=mp4_path, ffmpeg_path=ffmpeg_path, gif_width=gif_width, gif_fps=gif_fps, gif_max_seconds=gif_max_seconds, gif_suffix=gif_suffix ) previous_files = set() last_change_time = time.time() if reset_on_video: print("[i] Timer reset after video creation.") start_time = time.time() print("[✓] Monitoring stopped.") # ----------------- Entry ----------------- if __name__ == "__main__": try: settings = load_config() monitor(settings) except KeyboardInterrupt: print("\n[✓] Monitoring stopped by user.") The updated watchfolder.ini settings file: [settings] watch_dir = F:/renderfolder ffmpeg_path = ffmpeg framerate = 24 timeout = 5 video_basename = video max_runtime_minutes = 30 reset_timeout_on_video = true make_gif = true gif_max_seconds = 6 ; 0 = full length gif_width = 320 ; scaled width, height auto-preserved gif_fps = 12 ; frames per second in GIF gif_suffix = _thumb ; appended before .gif
-
I returned to this to add a new option. After successful creation of the MP4 video and moving of the PNG sequence the script creates a thumbnail gif animation using the MP4 video. A few observations. We can render to the watchfolder or simply copy/paste a sequence into the directory. Either way the watchfolder script will see new images arrive and respond accordingly. I fired up Netrender and rendered to the watchfolder** and any excuse to use Netrender is a good excuse right? This isn't using Netrender's native ability to run scripts after completion but that might be something to consider as we could have Netrender communicate with the Watchfolder script to pass project names and more over to the script. One thing I forgot in the interim from using the watchfolder utility was that the .ini settings override the settings in the script itself so I kept wondering why even though I had changed the location of the watchfolder in the script it refused to watch the directory I specified. Well, Rodney, that's because computers only do what you tell them to do and you told this one to use the directory set in the .ini file. Once that was updated... all very good! At any rate, render a sequence of PNGs and automatically get a MP4 video, a smaller gif animation preview and datetime stamp a directory holding all of the PNGs. Rather quick too I must say. And while we are talking utilities to work with A:M files... Here's a test of a program that visits a github repository, previews the file (if preview image found), allows the file to be downloaded AND, if a zip file is located in that resources directory activates a button to allow that zipfile to be downloaded. Its more of a proof of concept than anything very useful. I'd like to have the program look inside the Animation:Master resource and share the preview/icon image stored there (if present) and display the File Info text (if present). Now that I've experimented with extracting the icon previews out of A:M files I think I might be up to that challenge. Note1: The program first looks for a preview that belongs to the actual resource. For instance, if cube.mdl has a PNG image in same directory named cube_preview.png it will display that. If that preview is not present the program will look for a preview.png image in that directory.. If that image isn't present it displays a default image. It'd be overkill but fun to have it have an option to look for an animated gif. Note2: The token field is what allows more usage via github. Github tokens can be set to expire and the one I'm using in this test expires in September. When scanning through a large repository the user will run out of free access quickly so having the token helps a lot. Without token: 60 requests per hour With token: 5000 requests per hour The count resets every hour. Note3: This demo does not use git although there is no reason why it couldn't be added so that models could also be uploaded to the repository. The exploration here was focused accessing resources that are online.
-
We shall try to look at this at Live Answer Time if you can be there, Edward. I think I will need you to walk me through what you are doing.
-
Sorry, meant to ask if anyone has suggestions about bone heirarchy to avoid shrinkage when the arm is raised straight up. This setup follows Mike Fitzgerald's setup from his rigging DVD with officer McKnuckles.
-
svetlik started following SmartSkin Arm/Shoulder Progress
-
Made 2 seperate smartskins for bicep & forearm and driving them both with ArmsShoulders pose & making some progress. Not sure if that's the correct way to set that up but so far so good. See attached Prj. SuitCoat3.prj
-
Bring your A:M questions to LIve Answer Time at Noon CDT Saturday August 9, 2025! Comedian David Steinberg was born on this day in 1942. in 1968 Steinberg made the first of his 130+ appearance on "The Tonight Show"
-
Digging through my HD again looking for some old projects. I was commissioned at one point by Hash to model Batman, such a fun project, thought now days I would probably model him with half as many patches, always less is more.. especially with splines.
-
Hi Pete, Hope you are able to get the latest version of A:M running! sounds like an impressive PC and always nice to see old/new users using A:M and posting their cool artwork on here!
- Earlier
-
You can still buy Animation:Master. I always buy the annual subscription. I've NEVER had trouble getting the activation code. It always arrives instantly. I'm baffled when people have trouble with that. What are they doing differently? I have no idea. I've never bought the permanent version. Is that somehow different to activate? I don't know. If you have trouble that has to be resolved by email that will not be instant but it will get resolved eventually.
-
...what's going on. I still run A:M on a legacy Mac Pro. I just bought a new PC specifically and only to run the new versions of A:M. But can I still buy Hash? I get the impression it's kind of a side project now and the support staff is very busy with other things. I perfectly understand how hard 5 spare minutes can be to find sometimes. If I order a permanent license from the Hash Store, will I get my activation code email right away, or whenever the staff finds the time? I'm eager to see what this Alienware gadget can do and Hash A:M is the only reason I bought it. Right now it's just sitting there receiving spam from Microsoft. Thanks.
-
A Smartskin is a Relationship, driven by a Bone angle instead of a Pose slider value.